<!DOCTYPE html>
<html lang="en">
    <head hexo-theme='https://github.com/volantis-x/hexo-theme-volantis/#5.8.0'>
  <meta name="generator" content="Hexo 6.3.0">
  <meta name="Volantis" content="5.8.0">
  <meta charset="utf-8">
  <!-- SEO相关 -->
  
  <link rel="canonical" href="http://8.219.11.28/2024/02/23/前后端-springsecurity/"/>
  <!-- 渲染优化 -->
    <meta http-equiv='x-dns-prefetch-control' content='on' />
      <link rel='dns-prefetch' href='https://unpkg.com'>
      <link rel="preconnect" href="https://unpkg.com" crossorigin>
  <meta name="renderer" content="webkit">
  <meta name="force-rendering" content="webkit">
  <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
    <meta http-equiv="Content-Security-Policy" content=" default-src 'self' https:; block-all-mixed-content; base-uri 'self' https:; form-action 'self' https:; worker-src 'self' https:; connect-src 'self' https: *; img-src 'self' data: https: *; media-src 'self' https: *; font-src 'self' data: https: *; frame-src 'self' https: *; manifest-src 'self' https: *; child-src https:; script-src 'self' https: 'unsafe-inline' *; style-src 'self' https: 'unsafe-inline' *; ">
  <meta name="HandheldFriendly" content="True" >
  <meta name="apple-mobile-web-app-capable" content="yes">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5">
  <meta content="black-translucent" name="apple-mobile-web-app-status-bar-style">
  <meta content="telephone=no" name="format-detection">
  <!-- import head_begin begin -->
  <!-- import head_begin end -->
  <!-- Custom Files headBegin begin-->
  
  <!-- Custom Files headBegin end-->
  <!-- front-matter head_begin begin -->
  <!-- front-matter head_begin end -->
  <link rel="preload" href="/css/style.css" as="style">
  <link rel="preload" href="https://unpkg.com/volantis-static@0.0.1654736714924/media/fonts/VarelaRound/VarelaRound-Regular.ttf" as="font" type="font/ttf" crossorigin="anonymous">
<link rel="preload" href="https://unpkg.com/volantis-static@0.0.1654736714924/media/fonts/UbuntuMono/UbuntuMono-Regular.ttf" as="font" type="font/ttf" crossorigin="anonymous">

  <!-- feed -->
  <!-- 页面元数据 -->
  <title>欢迎来到我的个人博客~</title>
  <meta name="keywords" content="null">
  <meta desc name="description" content="这些技术大部分是自己总结的，非官方 - 天明 - 欢迎来到我的个人博客~">
  
<meta property="og:type" content="article">
<meta property="og:title" content="欢迎来到我的个人博客~">
<meta property="og:url" content="http://8.219.11.28/2024/02/23/%E5%89%8D%E5%90%8E%E7%AB%AF-SpringSecurity/index.html">
<meta property="og:site_name" content="欢迎来到我的个人博客~">
<meta property="og:description" content="SpringSecurity概念Spring家族当中，一个安全管理框架。 Shiro也是一个安全框架，提供了很多安全功能。Shiro比较老，旧的项目当中，可能还在使用。上手还挺简单。 在新项目当中，一线互联网大型项目，都是使用SpringSecurity 。 认证 鉴权一般的web项目当中，总会有登陆和鉴权的需求。但是大家一定要区分开。  认证：验证当前访问的用户是不是本系统中的用户。确定是哪一个">
<meta property="og:locale" content="en_US">
<meta property="og:image" content="https://unpkg.com/volantis-static@0.0.1654736714924/media/org.volantis/blog/favicon/android-chrome-192x192.png">
<meta property="article:published_time" content="2024-02-23T14:27:45.499Z">
<meta property="article:modified_time" content="2024-01-24T05:24:15.042Z">
<meta property="article:author" content="天明">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://unpkg.com/volantis-static@0.0.1654736714924/media/org.volantis/blog/favicon/android-chrome-192x192.png">
  <style>
    /* 首屏样式 */
    #safearea {
  display: none;
}
:root {
  --color-site-body: #f4f4f4;
  --color-site-bg: #f4f4f4;
  --color-site-inner: #fff;
  --color-site-footer: #666;
  --color-card: #fff;
  --color-text: #444;
  --color-block: #f6f6f6;
  --color-inlinecode: #c74f00;
  --color-codeblock: #fff7ea;
  --color-h1: #3a3a3a;
  --color-h2: #3a3a3a;
  --color-h3: #333;
  --color-h4: #444;
  --color-h5: #555;
  --color-h6: #666;
  --color-p: #444;
  --color-list: #666;
  --color-list-hl: #30ad91;
  --color-meta: #888;
  --color-read-bkg: #e0d8c8;
  --color-read-post: #f8f1e2;
  --color-copyright-bkg: #f5f5f5;
}
* {
  box-sizing: border-box;
  -webkit-box-sizing: border-box;
  -moz-box-sizing: border-box;
  outline: none;
  margin: 0;
  padding: 0;
}
*::-webkit-scrollbar {
  height: 4px;
  width: 4px;
}
*::-webkit-scrollbar-track-piece {
  background: transparent;
}
*::-webkit-scrollbar-thumb {
  background: #3dd9b6;
  cursor: pointer;
  border-radius: 2px;
  -webkit-border-radius: 2px;
}
*::-webkit-scrollbar-thumb:hover {
  background: #ff5722;
}
html {
  color: var(--color-text);
  width: 100%;
  height: 100%;
  font-family: UbuntuMono, "Varela Round", "PingFang SC", "Microsoft YaHei", Helvetica, Arial, Menlo, Monaco, monospace, sans-serif;
  font-size: 16px;
}
html >::-webkit-scrollbar {
  height: 4px;
  width: 4px;
}
html >::-webkit-scrollbar-track-piece {
  background: transparent;
}
html >::-webkit-scrollbar-thumb {
  background: #54b5a0 linear-gradient(45deg, rgba(255,255,255,0.4) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.4) 50%, rgba(255,255,255,0.4) 75%, transparent 75%, transparent);
  cursor: pointer;
  border-radius: 2px;
  -webkit-border-radius: 2px;
}
html >::-webkit-scrollbar-thumb:hover {
  background: #54b5a0 linear-gradient(45deg, rgba(255,255,255,0.4) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.4) 50%, rgba(255,255,255,0.4) 75%, transparent 75%, transparent);
}
body {
  background-color: var(--color-site-body);
  text-rendering: optimizelegibility;
  -webkit-tap-highlight-color: rgba(0,0,0,0);
  line-height: 1.6;
  -webkit-text-size-adjust: 100%;
  -ms-text-size-adjust: 100%;
}
body.modal-active {
  overflow: hidden;
}
@media screen and (max-width: 680px) {
  body.modal-active {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
  }
}
a {
  color: #2092ec;
  cursor: pointer;
  text-decoration: none;
  transition: all 0.28s ease;
  -webkit-transition: all 0.28s ease;
  -khtml-transition: all 0.28s ease;
  -moz-transition: all 0.28s ease;
  -o-transition: all 0.28s ease;
  -ms-transition: all 0.28s ease;
}
a:hover {
  color: #ff5722;
}
a:active,
a:hover {
  outline: 0;
}
ul,
ol {
  padding-left: 0;
}
ul li,
ol li {
  list-style: none;
}
header {
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: block;
}
img {
  border: 0;
  background: none;
  max-width: 100%;
}
svg:not(:root) {
  overflow: hidden;
}
hr {
  -moz-box-sizing: content-box;
  box-sizing: content-box;
  -webkit-box-sizing: content-box;
  -moz-box-sizing: content-box;
  height: 0;
  border: 0;
  border-radius: 1px;
  -webkit-border-radius: 1px;
  border-bottom: 1px solid rgba(68,68,68,0.1);
}
button,
input {
  color: inherit;
  font: inherit;
  margin: 0;
}
button {
  overflow: visible;
  text-transform: none;
  -webkit-appearance: button;
  cursor: pointer;
}
@supports (backdrop-filter: blur(20px)) {
  .blur {
    background: rgba(255,255,255,0.9) !important;
    backdrop-filter: saturate(200%) blur(20px);
  }
}
.shadow {
  box-shadow: 0 1px 2px 0px rgba(0,0,0,0.1);
  -webkit-box-shadow: 0 1px 2px 0px rgba(0,0,0,0.1);
}
.shadow.floatable {
  transition: all 0.28s ease;
  -webkit-transition: all 0.28s ease;
  -khtml-transition: all 0.28s ease;
  -moz-transition: all 0.28s ease;
  -o-transition: all 0.28s ease;
  -ms-transition: all 0.28s ease;
}
.shadow.floatable:hover {
  box-shadow: 0 2px 4px 0px rgba(0,0,0,0.1), 0 4px 8px 0px rgba(0,0,0,0.1), 0 8px 16px 0px rgba(0,0,0,0.1);
  -webkit-box-shadow: 0 2px 4px 0px rgba(0,0,0,0.1), 0 4px 8px 0px rgba(0,0,0,0.1), 0 8px 16px 0px rgba(0,0,0,0.1);
}
#l_cover {
  min-height: 64px;
}
.cover-wrapper {
  top: 0;
  left: 0;
  max-width: 100%;
  height: 100vh;
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: -ms-flexbox /* TWEENER - IE 10 */;
  display: -webkit-flex /* NEW - Chrome */;
  display: flex /* NEW, Spec - Opera 12.1, Firefox 20+ */;
  display: flex;
  flex-wrap: nowrap;
  -webkit-flex-wrap: nowrap;
  -khtml-flex-wrap: nowrap;
  -moz-flex-wrap: nowrap;
  -o-flex-wrap: nowrap;
  -ms-flex-wrap: nowrap;
  -webkit-box-direction: normal;
  -moz-box-direction: normal;
  -webkit-box-orient: vertical;
  -moz-box-orient: vertical;
  -webkit-flex-direction: column;
  -ms-flex-direction: column;
  flex-direction: column;
  align-items: center;
  align-self: center;
  align-content: center;
  color: var(--color-site-inner);
  padding: 0 16px;
  user-select: none;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  position: relative;
  overflow: hidden;
  margin-bottom: -100px;
}
.cover-wrapper .cover-bg {
  position: absolute;
  width: 100%;
  height: 100%;
  background-position: center;
  background-size: cover;
  -webkit-background-size: cover;
  -moz-background-size: cover;
}
.cover-wrapper .cover-bg.lazyload:not(.loaded) {
  opacity: 0;
  -webkit-opacity: 0;
  -moz-opacity: 0;
}
.cover-wrapper .cover-bg.lazyload.loaded {
  animation-delay: 0s;
  animation-duration: 0.5s;
  animation-fill-mode: forwards;
  animation-timing-function: ease-out;
  animation-name: fadeIn;
}
@-moz-keyframes fadeIn {
  0% {
    opacity: 0;
    -webkit-opacity: 0;
    -moz-opacity: 0;
    filter: blur(12px);
    transform: scale(1.02);
    -webkit-transform: scale(1.02);
    -khtml-transform: scale(1.02);
    -moz-transform: scale(1.02);
    -o-transform: scale(1.02);
    -ms-transform: scale(1.02);
  }
  100% {
    opacity: 1;
    -webkit-opacity: 1;
    -moz-opacity: 1;
  }
}
@-webkit-keyframes fadeIn {
  0% {
    opacity: 0;
    -webkit-opacity: 0;
    -moz-opacity: 0;
    filter: blur(12px);
    transform: scale(1.02);
    -webkit-transform: scale(1.02);
    -khtml-transform: scale(1.02);
    -moz-transform: scale(1.02);
    -o-transform: scale(1.02);
    -ms-transform: scale(1.02);
  }
  100% {
    opacity: 1;
    -webkit-opacity: 1;
    -moz-opacity: 1;
  }
}
@-o-keyframes fadeIn {
  0% {
    opacity: 0;
    -webkit-opacity: 0;
    -moz-opacity: 0;
    filter: blur(12px);
    transform: scale(1.02);
    -webkit-transform: scale(1.02);
    -khtml-transform: scale(1.02);
    -moz-transform: scale(1.02);
    -o-transform: scale(1.02);
    -ms-transform: scale(1.02);
  }
  100% {
    opacity: 1;
    -webkit-opacity: 1;
    -moz-opacity: 1;
  }
}
@keyframes fadeIn {
  0% {
    opacity: 0;
    -webkit-opacity: 0;
    -moz-opacity: 0;
    filter: blur(12px);
    transform: scale(1.02);
    -webkit-transform: scale(1.02);
    -khtml-transform: scale(1.02);
    -moz-transform: scale(1.02);
    -o-transform: scale(1.02);
    -ms-transform: scale(1.02);
  }
  100% {
    opacity: 1;
    -webkit-opacity: 1;
    -moz-opacity: 1;
  }
}
.cover-wrapper .cover-body {
  z-index: 1;
  position: relative;
  width: 100%;
  height: 100%;
}
.cover-wrapper#full {
  height: calc(100vh + 100px);
  padding-bottom: 100px;
}
.cover-wrapper#half {
  max-height: 640px;
  min-height: 400px;
  height: calc(36vh - 64px + 200px);
}
.cover-wrapper #scroll-down {
  width: 100%;
  height: 64px;
  position: absolute;
  bottom: 100px;
  text-align: center;
  cursor: pointer;
}
.cover-wrapper #scroll-down .scroll-down-effects {
  color: #fff;
  font-size: 24px;
  line-height: 64px;
  position: absolute;
  width: 24px;
  left: calc(50% - 12px);
  text-shadow: 0 1px 2px rgba(0,0,0,0.1);
  animation: scroll-down-effect 1.5s infinite;
  -webkit-animation: scroll-down-effect 1.5s infinite;
  -khtml-animation: scroll-down-effect 1.5s infinite;
  -moz-animation: scroll-down-effect 1.5s infinite;
  -o-animation: scroll-down-effect 1.5s infinite;
  -ms-animation: scroll-down-effect 1.5s infinite;
}
@-moz-keyframes scroll-down-effect {
  0% {
    top: 0;
    opacity: 1;
    -webkit-opacity: 1;
    -moz-opacity: 1;
  }
  50% {
    top: -16px;
    opacity: 0.4;
    -webkit-opacity: 0.4;
    -moz-opacity: 0.4;
  }
  100% {
    top: 0;
    opacity: 1;
    -webkit-opacity: 1;
    -moz-opacity: 1;
  }
}
@-webkit-keyframes scroll-down-effect {
  0% {
    top: 0;
    opacity: 1;
    -webkit-opacity: 1;
    -moz-opacity: 1;
  }
  50% {
    top: -16px;
    opacity: 0.4;
    -webkit-opacity: 0.4;
    -moz-opacity: 0.4;
  }
  100% {
    top: 0;
    opacity: 1;
    -webkit-opacity: 1;
    -moz-opacity: 1;
  }
}
@-o-keyframes scroll-down-effect {
  0% {
    top: 0;
    opacity: 1;
    -webkit-opacity: 1;
    -moz-opacity: 1;
  }
  50% {
    top: -16px;
    opacity: 0.4;
    -webkit-opacity: 0.4;
    -moz-opacity: 0.4;
  }
  100% {
    top: 0;
    opacity: 1;
    -webkit-opacity: 1;
    -moz-opacity: 1;
  }
}
@keyframes scroll-down-effect {
  0% {
    top: 0;
    opacity: 1;
    -webkit-opacity: 1;
    -moz-opacity: 1;
  }
  50% {
    top: -16px;
    opacity: 0.4;
    -webkit-opacity: 0.4;
    -moz-opacity: 0.4;
  }
  100% {
    top: 0;
    opacity: 1;
    -webkit-opacity: 1;
    -moz-opacity: 1;
  }
}
.cover-wrapper .cover-body {
  margin-top: 64px;
  margin-bottom: 100px;
}
.cover-wrapper .cover-body,
.cover-wrapper .cover-body .top,
.cover-wrapper .cover-body .bottom {
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: -ms-flexbox /* TWEENER - IE 10 */;
  display: -webkit-flex /* NEW - Chrome */;
  display: flex /* NEW, Spec - Opera 12.1, Firefox 20+ */;
  display: flex;
  -webkit-box-direction: normal;
  -moz-box-direction: normal;
  -webkit-box-orient: vertical;
  -moz-box-orient: vertical;
  -webkit-flex-direction: column;
  -ms-flex-direction: column;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  -webkit-justify-content: center;
  -khtml-justify-content: center;
  -moz-justify-content: center;
  -o-justify-content: center;
  -ms-justify-content: center;
  max-width: 100%;
}
.cover-wrapper .cover-body .bottom {
  margin-top: 32px;
}
.cover-wrapper .cover-body .title {
  font-family: "Varela Round", "PingFang SC", "Microsoft YaHei", Helvetica, Arial, Helvetica, monospace;
  font-size: 3.125rem;
  line-height: 1.2;
  text-shadow: 0 1px 2px rgba(0,0,0,0.1);
}
.cover-wrapper .cover-body .subtitle {
  font-size: 20px;
}
.cover-wrapper .cover-body .logo {
  max-height: 120px;
  max-width: calc(100% - 4 * 16px);
}
@media screen and (min-height: 1024px) {
  .cover-wrapper .cover-body .title {
    font-size: 3rem;
  }
  .cover-wrapper .cover-body .subtitle {
    font-size: 1.05rem;
  }
  .cover-wrapper .cover-body .logo {
    max-height: 150px;
  }
}
.cover-wrapper .cover-body .m_search {
  position: relative;
  max-width: calc(100% - 16px);
  width: 320px;
  vertical-align: middle;
}
.cover-wrapper .cover-body .m_search .form {
  position: relative;
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: block;
  width: 100%;
}
.cover-wrapper .cover-body .m_search .icon,
.cover-wrapper .cover-body .m_search .input {
  transition: all 0.28s ease;
  -webkit-transition: all 0.28s ease;
  -khtml-transition: all 0.28s ease;
  -moz-transition: all 0.28s ease;
  -o-transition: all 0.28s ease;
  -ms-transition: all 0.28s ease;
}
.cover-wrapper .cover-body .m_search .icon {
  position: absolute;
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: block;
  line-height: 2.5rem;
  width: 32px;
  top: 0;
  left: 5px;
  color: rgba(68,68,68,0.75);
}
.cover-wrapper .cover-body .m_search .input {
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: block;
  height: 2.5rem;
  width: 100%;
  box-shadow: none;
  -webkit-box-shadow: none;
  box-sizing: border-box;
  -webkit-box-sizing: border-box;
  -moz-box-sizing: border-box;
  font-size: 0.875rem;
  -webkit-appearance: none;
  padding-left: 36px;
  border-radius: 1.4rem;
  -webkit-border-radius: 1.4rem;
  background: rgba(255,255,255,0.6);
  backdrop-filter: blur(10px);
  border: none;
  color: var(--color-text);
}
@media screen and (max-width: 500px) {
  .cover-wrapper .cover-body .m_search .input {
    padding-left: 36px;
  }
}
.cover-wrapper .cover-body .m_search .input:hover {
  background: rgba(255,255,255,0.8);
}
.cover-wrapper .cover-body .m_search .input:focus {
  background: #fff;
}
.cover-wrapper .list-h {
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: -ms-flexbox /* TWEENER - IE 10 */;
  display: -webkit-flex /* NEW - Chrome */;
  display: flex /* NEW, Spec - Opera 12.1, Firefox 20+ */;
  display: flex;
  -webkit-box-direction: normal;
  -moz-box-direction: normal;
  -webkit-box-orient: horizontal;
  -moz-box-orient: horizontal;
  -webkit-flex-direction: row;
  -ms-flex-direction: row;
  flex-direction: row;
  flex-wrap: wrap;
  -webkit-flex-wrap: wrap;
  -khtml-flex-wrap: wrap;
  -moz-flex-wrap: wrap;
  -o-flex-wrap: wrap;
  -ms-flex-wrap: wrap;
  align-items: stretch;
  border-radius: 4px;
  -webkit-border-radius: 4px;
  user-select: none;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
}
.cover-wrapper .list-h a {
  -webkit-box-flex: 1;
  -moz-box-flex: 1;
  -webkit-flex: 1 0;
  -ms-flex: 1 0;
  flex: 1 0;
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: -ms-flexbox /* TWEENER - IE 10 */;
  display: -webkit-flex /* NEW - Chrome */;
  display: flex /* NEW, Spec - Opera 12.1, Firefox 20+ */;
  display: flex;
  font-weight: 600;
}
.cover-wrapper .list-h a img {
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: block;
  border-radius: 2px;
  -webkit-border-radius: 2px;
  margin: 4px;
  min-width: 40px;
  max-width: 44px;
}
@media screen and (max-width: 768px) {
  .cover-wrapper .list-h a img {
    min-width: 36px;
    max-width: 40px;
  }
}
@media screen and (max-width: 500px) {
  .cover-wrapper .list-h a img {
    margin: 2px 4px;
    min-width: 32px;
    max-width: 36px;
  }
}
@media screen and (max-width: 375px) {
  .cover-wrapper .list-h a img {
    min-width: 28px;
    max-width: 32px;
  }
}
.cover-wrapper {
  max-width: 100%;
}
.cover-wrapper.search .bottom .menu {
  margin-top: 16px;
}
.cover-wrapper.search .bottom .menu .list-h a {
  white-space: nowrap;
  -webkit-box-direction: normal;
  -moz-box-direction: normal;
  -webkit-box-orient: horizontal;
  -moz-box-orient: horizontal;
  -webkit-flex-direction: row;
  -ms-flex-direction: row;
  flex-direction: row;
  align-items: baseline;
  padding: 2px;
  margin: 4px;
  color: var(--color-site-inner);
  opacity: 0.75;
  -webkit-opacity: 0.75;
  -moz-opacity: 0.75;
  text-shadow: 0 1px 2px rgba(0,0,0,0.05);
  border-bottom: 2px solid transparent;
}
.cover-wrapper.search .bottom .menu .list-h a i {
  margin-right: 4px;
}
.cover-wrapper.search .bottom .menu .list-h a p {
  font-size: 0.9375rem;
}
.cover-wrapper.search .bottom .menu .list-h a:hover,
.cover-wrapper.search .bottom .menu .list-h a.active,
.cover-wrapper.search .bottom .menu .list-h a:active {
  opacity: 1;
  -webkit-opacity: 1;
  -moz-opacity: 1;
  border-bottom: 2px solid var(--color-site-inner);
}
.cover-wrapper.dock .menu,
.cover-wrapper.featured .menu,
.cover-wrapper.focus .menu {
  border-radius: 6px;
  -webkit-border-radius: 6px;
}
.cover-wrapper.dock .menu .list-h a,
.cover-wrapper.featured .menu .list-h a,
.cover-wrapper.focus .menu .list-h a {
  -webkit-box-direction: normal;
  -moz-box-direction: normal;
  -webkit-box-orient: vertical;
  -moz-box-orient: vertical;
  -webkit-flex-direction: column;
  -ms-flex-direction: column;
  flex-direction: column;
  align-items: center;
  padding: 12px;
  line-height: 24px;
  border-radius: 4px;
  -webkit-border-radius: 4px;
  border-bottom: none;
  text-align: center;
  align-content: flex-end;
  color: rgba(68,68,68,0.7);
  font-size: 1.5rem;
}
@media screen and (max-width: 500px) {
  .cover-wrapper.dock .menu .list-h a,
  .cover-wrapper.featured .menu .list-h a,
  .cover-wrapper.focus .menu .list-h a {
    padding: 12px 8px;
  }
}
.cover-wrapper.dock .menu .list-h a i,
.cover-wrapper.featured .menu .list-h a i,
.cover-wrapper.focus .menu .list-h a i {
  margin: 8px;
}
.cover-wrapper.dock .menu .list-h a p,
.cover-wrapper.featured .menu .list-h a p,
.cover-wrapper.focus .menu .list-h a p {
  font-size: 0.875rem;
}
.cover-wrapper.dock .menu .list-h a.active,
.cover-wrapper.featured .menu .list-h a.active,
.cover-wrapper.focus .menu .list-h a.active {
  background: var(--color-card);
  backdrop-filter: none;
}
.cover-wrapper.dock .menu .list-h a.active i,
.cover-wrapper.featured .menu .list-h a.active i,
.cover-wrapper.focus .menu .list-h a.active i,
.cover-wrapper.dock .menu .list-h a.active i+p,
.cover-wrapper.featured .menu .list-h a.active i+p,
.cover-wrapper.focus .menu .list-h a.active i+p {
  color: #3dd9b6;
}
.cover-wrapper.dock .menu .list-h a.active img+p,
.cover-wrapper.featured .menu .list-h a.active img+p,
.cover-wrapper.focus .menu .list-h a.active img+p {
  color: var(--color-text);
}
.cover-wrapper.dock .menu .list-h a:hover,
.cover-wrapper.featured .menu .list-h a:hover,
.cover-wrapper.focus .menu .list-h a:hover {
  background: var(--color-card);
}
.cover-wrapper.dock .top {
  margin-bottom: 48px;
}
.cover-wrapper.dock .menu {
  background: rgba(255,255,255,0.5);
  position: absolute;
  bottom: 0;
  max-width: 100%;
}
.cover-wrapper.dock .menu .list-h {
  flex-wrap: nowrap;
  -webkit-flex-wrap: nowrap;
  -khtml-flex-wrap: nowrap;
  -moz-flex-wrap: nowrap;
  -o-flex-wrap: nowrap;
  -ms-flex-wrap: nowrap;
  margin: 4px;
}
.cover-wrapper.dock .menu .list-h a+a {
  margin-left: 4px;
}
@media screen and (max-width: 500px) {
  .cover-wrapper.dock .menu .list-h {
    overflow-x: scroll;
  }
  .cover-wrapper.dock .menu .list-h::-webkit-scrollbar {
    height: 0;
    width: 0;
  }
  .cover-wrapper.dock .menu .list-h::-webkit-scrollbar-track-piece {
    background: transparent;
  }
  .cover-wrapper.dock .menu .list-h::-webkit-scrollbar-thumb {
    background: #3dd9b6;
    cursor: pointer;
    border-radius: 0;
    -webkit-border-radius: 0;
  }
  .cover-wrapper.dock .menu .list-h::-webkit-scrollbar-thumb:hover {
    background: #ff5722;
  }
}
@supports (backdrop-filter: blur(20px)) {
  .cover-wrapper.dock .menu {
    background: rgba(255,255,255,0.5);
    backdrop-filter: saturate(200%) blur(20px);
  }
}
@font-face {
  font-family: 'UbuntuMono';
  src: url("https://unpkg.com/volantis-static@0.0.1654736714924/media/fonts/UbuntuMono/UbuntuMono-Regular.ttf");
  font-weight: 'normal';
  font-style: 'normal';
  font-display: swap;
}
@font-face {
  font-family: 'Varela Round';
  src: url("https://unpkg.com/volantis-static@0.0.1654736714924/media/fonts/VarelaRound/VarelaRound-Regular.ttf");
  font-weight: 'normal';
  font-style: 'normal';
  font-display: swap;
}
.l_header {
  position: fixed;
  z-index: 1000;
  top: 0;
  width: 100%;
  height: 64px;
  background: var(--color-card);
  box-shadow: 0 1px 2px 0px rgba(0,0,0,0.1);
  -webkit-box-shadow: 0 1px 2px 0px rgba(0,0,0,0.1);
}
.l_header.auto {
  transition: opacity 0.4s ease;
  -webkit-transition: opacity 0.4s ease;
  -khtml-transition: opacity 0.4s ease;
  -moz-transition: opacity 0.4s ease;
  -o-transition: opacity 0.4s ease;
  -ms-transition: opacity 0.4s ease;
  visibility: hidden;
}
.l_header.auto.show {
  opacity: 1 !important;
  -webkit-opacity: 1 !important;
  -moz-opacity: 1 !important;
  visibility: visible;
}
.l_header .container {
  margin-left: 16px;
  margin-right: 16px;
}
.l_header #wrapper {
  height: 100%;
  user-select: none;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
}
.l_header #wrapper .nav-main,
.l_header #wrapper .nav-sub {
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: -ms-flexbox /* TWEENER - IE 10 */;
  display: -webkit-flex /* NEW - Chrome */;
  display: flex /* NEW, Spec - Opera 12.1, Firefox 20+ */;
  display: flex;
  flex-wrap: nowrap;
  -webkit-flex-wrap: nowrap;
  -khtml-flex-wrap: nowrap;
  -moz-flex-wrap: nowrap;
  -o-flex-wrap: nowrap;
  -ms-flex-wrap: nowrap;
  justify-content: space-between;
  -webkit-justify-content: space-between;
  -khtml-justify-content: space-between;
  -moz-justify-content: space-between;
  -o-justify-content: space-between;
  -ms-justify-content: space-between;
  align-items: center;
}
.l_header #wrapper .nav-main {
  transition: all 0.28s ease;
  -webkit-transition: all 0.28s ease;
  -khtml-transition: all 0.28s ease;
  -moz-transition: all 0.28s ease;
  -o-transition: all 0.28s ease;
  -ms-transition: all 0.28s ease;
}
.l_header #wrapper.sub .nav-main {
  transform: translateY(-64px);
  -webkit-transform: translateY(-64px);
  -khtml-transform: translateY(-64px);
  -moz-transform: translateY(-64px);
  -o-transform: translateY(-64px);
  -ms-transform: translateY(-64px);
}
.l_header #wrapper .nav-sub {
  transition: all 0.28s ease;
  -webkit-transition: all 0.28s ease;
  -khtml-transition: all 0.28s ease;
  -moz-transition: all 0.28s ease;
  -o-transition: all 0.28s ease;
  -ms-transition: all 0.28s ease;
  opacity: 0;
  -webkit-opacity: 0;
  -moz-opacity: 0;
  height: 64px;
  width: calc(100% - 2 * 16px);
  position: absolute;
}
.l_header #wrapper .nav-sub ::-webkit-scrollbar {
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: none;
}
@media screen and (min-width: 2048px) {
  .l_header #wrapper .nav-sub {
    max-width: 55vw;
    margin: auto;
  }
}
.l_header #wrapper.sub .nav-sub {
  opacity: 1;
  -webkit-opacity: 1;
  -moz-opacity: 1;
}
.l_header #wrapper .title {
  position: relative;
  color: var(--color-text);
  padding-left: 24px;
  max-height: 64px;
}
.l_header #wrapper .nav-main .title {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  flex-shrink: 0;
  line-height: 64px;
  padding: 0 24px;
  font-size: 1.25rem;
  font-family: "Varela Round", "PingFang SC", "Microsoft YaHei", Helvetica, Arial, Helvetica, monospace;
}
.l_header #wrapper .nav-main .title img {
  height: 64px;
}
.l_header .nav-sub {
  max-width: 1080px;
  margin: auto;
}
.l_header .nav-sub .title {
  font-weight: bold;
  font-family: UbuntuMono, "Varela Round", "PingFang SC", "Microsoft YaHei", Helvetica, Arial, Menlo, Monaco, monospace, sans-serif;
  line-height: 1.2;
  max-height: 64px;
  white-space: normal;
  flex-shrink: 1;
}
.l_header .switcher {
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: none;
  line-height: 64px;
  align-items: center;
}
.l_header .switcher .s-toc {
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: none;
}
@media screen and (max-width: 768px) {
  .l_header .switcher .s-toc {
    display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
    display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
    display: -ms-flexbox /* TWEENER - IE 10 */;
    display: -webkit-flex /* NEW - Chrome */;
    display: flex /* NEW, Spec - Opera 12.1, Firefox 20+ */;
    display: flex;
  }
}
.l_header .switcher >li {
  height: 48px;
  transition: all 0.28s ease;
  -webkit-transition: all 0.28s ease;
  -khtml-transition: all 0.28s ease;
  -moz-transition: all 0.28s ease;
  -o-transition: all 0.28s ease;
  -ms-transition: all 0.28s ease;
  margin: 2px;
}
@media screen and (max-width: 500px) {
  .l_header .switcher >li {
    margin: 0 1px;
    height: 48px;
  }
}
.l_header .switcher >li >a {
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: -ms-flexbox /* TWEENER - IE 10 */;
  display: -webkit-flex /* NEW - Chrome */;
  display: flex /* NEW, Spec - Opera 12.1, Firefox 20+ */;
  display: flex;
  justify-content: center;
  -webkit-justify-content: center;
  -khtml-justify-content: center;
  -moz-justify-content: center;
  -o-justify-content: center;
  -ms-justify-content: center;
  align-items: center;
  width: 48px;
  height: 48px;
  padding: 0.85em 1.1em;
  border-radius: 100px;
  -webkit-border-radius: 100px;
  border: none;
  transition: all 0.28s ease;
  -webkit-transition: all 0.28s ease;
  -khtml-transition: all 0.28s ease;
  -moz-transition: all 0.28s ease;
  -o-transition: all 0.28s ease;
  -ms-transition: all 0.28s ease;
  color: #3dd9b6;
}
.l_header .switcher >li >a:hover {
  border: none;
}
.l_header .switcher >li >a.active,
.l_header .switcher >li >a:active {
  border: none;
  background: var(--color-site-bg);
}
@media screen and (max-width: 500px) {
  .l_header .switcher >li >a {
    width: 36px;
    height: 48px;
  }
}
.l_header .nav-sub .switcher {
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: -ms-flexbox /* TWEENER - IE 10 */;
  display: -webkit-flex /* NEW - Chrome */;
  display: flex /* NEW, Spec - Opera 12.1, Firefox 20+ */;
  display: flex;
}
.l_header .m_search {
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: -ms-flexbox /* TWEENER - IE 10 */;
  display: -webkit-flex /* NEW - Chrome */;
  display: flex /* NEW, Spec - Opera 12.1, Firefox 20+ */;
  display: flex;
  height: 64px;
  width: 240px;
  transition: all 0.28s ease;
  -webkit-transition: all 0.28s ease;
  -khtml-transition: all 0.28s ease;
  -moz-transition: all 0.28s ease;
  -o-transition: all 0.28s ease;
  -ms-transition: all 0.28s ease;
}
@media screen and (max-width: 1024px) {
  .l_header .m_search {
    width: 44px;
    min-width: 44px;
  }
  .l_header .m_search input::placeholder {
    opacity: 0;
    -webkit-opacity: 0;
    -moz-opacity: 0;
  }
  .l_header .m_search:hover {
    width: 240px;
  }
  .l_header .m_search:hover input::placeholder {
    opacity: 1;
    -webkit-opacity: 1;
    -moz-opacity: 1;
  }
}
@media screen and (min-width: 500px) {
  .l_header .m_search:hover .input {
    width: 100%;
  }
  .l_header .m_search:hover .input::placeholder {
    opacity: 1;
    -webkit-opacity: 1;
    -moz-opacity: 1;
  }
}
@media screen and (max-width: 500px) {
  .l_header .m_search {
    min-width: 0;
  }
  .l_header .m_search input::placeholder {
    opacity: 1;
    -webkit-opacity: 1;
    -moz-opacity: 1;
  }
}
.l_header .m_search .form {
  position: relative;
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: -ms-flexbox /* TWEENER - IE 10 */;
  display: -webkit-flex /* NEW - Chrome */;
  display: flex /* NEW, Spec - Opera 12.1, Firefox 20+ */;
  display: flex;
  width: 100%;
  align-items: center;
}
.l_header .m_search .icon {
  position: absolute;
  width: 36px;
  left: 5px;
  color: var(--color-meta);
}
@media screen and (max-width: 500px) {
  .l_header .m_search .icon {
    display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
    display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
    display: none;
  }
}
.l_header .m_search .input {
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: block;
  padding-top: 8px;
  padding-bottom: 8px;
  line-height: 1.3;
  width: 100%;
  color: var(--color-text);
  background: #fafafa;
  box-shadow: none;
  -webkit-box-shadow: none;
  box-sizing: border-box;
  -webkit-box-sizing: border-box;
  -moz-box-sizing: border-box;
  padding-left: 40px;
  font-size: 0.875rem;
  border-radius: 8px;
  -webkit-border-radius: 8px;
  border: none;
  transition: all 0.28s ease;
  -webkit-transition: all 0.28s ease;
  -khtml-transition: all 0.28s ease;
  -moz-transition: all 0.28s ease;
  -o-transition: all 0.28s ease;
  -ms-transition: all 0.28s ease;
}
@media screen and (min-width: 500px) {
  .l_header .m_search .input:focus {
    box-shadow: 0 4px 8px 0px rgba(0,0,0,0.1);
    -webkit-box-shadow: 0 4px 8px 0px rgba(0,0,0,0.1);
  }
}
@media screen and (max-width: 500px) {
  .l_header .m_search .input {
    background: var(--color-block);
    padding-left: 8px;
    border: none;
  }
  .l_header .m_search .input:hover,
  .l_header .m_search .input:focus {
    border: none;
  }
}
@media (max-width: 500px) {
  .l_header .m_search {
    left: 0;
    width: 0;
    overflow: hidden;
    position: absolute;
    background: #fff;
    transition: all 0.28s ease;
    -webkit-transition: all 0.28s ease;
    -khtml-transition: all 0.28s ease;
    -moz-transition: all 0.28s ease;
    -o-transition: all 0.28s ease;
    -ms-transition: all 0.28s ease;
  }
  .l_header .m_search .input {
    border-radius: 32px;
    -webkit-border-radius: 32px;
    margin-left: 16px;
    padding-left: 16px;
  }
  .l_header.z_search-open .m_search {
    width: 100%;
  }
  .l_header.z_search-open .m_search .input {
    width: calc(100% - 120px);
  }
}
ul.m-pc >li>a {
  color: inherit;
  border-bottom: 2px solid transparent;
}
ul.m-pc >li>a:active,
ul.m-pc >li>a.active {
  border-bottom: 2px solid #3dd9b6;
}
ul.m-pc li:hover >ul.list-v,
ul.list-v li:hover >ul.list-v {
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: block;
}
ul.nav-list-h {
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: -ms-flexbox /* TWEENER - IE 10 */;
  display: -webkit-flex /* NEW - Chrome */;
  display: flex /* NEW, Spec - Opera 12.1, Firefox 20+ */;
  display: flex;
  align-items: stretch;
}
ul.nav-list-h>li {
  position: relative;
  justify-content: center;
  -webkit-justify-content: center;
  -khtml-justify-content: center;
  -moz-justify-content: center;
  -o-justify-content: center;
  -ms-justify-content: center;
  height: 100%;
  line-height: 2.4;
  border-radius: 4px;
  -webkit-border-radius: 4px;
}
ul.nav-list-h>li >a {
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-weight: 600;
}
ul.list-v {
  z-index: 1;
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: none;
  position: absolute;
  background: var(--color-card);
  box-shadow: 0 2px 4px 0px rgba(0,0,0,0.08), 0 4px 8px 0px rgba(0,0,0,0.08), 0 8px 16px 0px rgba(0,0,0,0.08);
  -webkit-box-shadow: 0 2px 4px 0px rgba(0,0,0,0.08), 0 4px 8px 0px rgba(0,0,0,0.08), 0 8px 16px 0px rgba(0,0,0,0.08);
  margin-top: -6px;
  border-radius: 4px;
  -webkit-border-radius: 4px;
  padding: 8px 0;
}
ul.list-v.show {
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: block;
}
ul.list-v hr {
  margin-top: 8px;
  margin-bottom: 8px;
}
ul.list-v >li {
  white-space: nowrap;
  word-break: keep-all;
}
ul.list-v >li.header {
  font-size: 0.78125rem;
  font-weight: bold;
  line-height: 2em;
  color: var(--color-meta);
  margin: 8px 16px 4px;
}
ul.list-v >li.header i {
  margin-right: 8px;
}
ul.list-v >li ul {
  margin-left: 0;
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: none;
  margin-top: -40px;
}
ul.list-v .aplayer-container {
  min-height: 64px;
  padding: 6px 16px;
}
ul.list-v >li>a {
  transition: all 0.28s ease;
  -webkit-transition: all 0.28s ease;
  -khtml-transition: all 0.28s ease;
  -moz-transition: all 0.28s ease;
  -o-transition: all 0.28s ease;
  -ms-transition: all 0.28s ease;
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: block;
  color: var(--color-list);
  font-size: 0.875rem;
  font-weight: bold;
  line-height: 36px;
  padding: 0 20px 0 16px;
  text-overflow: ellipsis;
  margin: 0 4px;
  border-radius: 4px;
  -webkit-border-radius: 4px;
}
@media screen and (max-width: 1024px) {
  ul.list-v >li>a {
    line-height: 40px;
  }
}
ul.list-v >li>a >i {
  margin-right: 8px;
}
ul.list-v >li>a:active,
ul.list-v >li>a.active {
  color: var(--color-list-hl);
}
ul.list-v >li>a:hover {
  color: var(--color-list-hl);
  background: var(--color-site-bg);
}
.l_header .menu >ul>li>a {
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: block;
  padding: 0 8px;
}
.l_header .menu >ul>li>a >i {
  margin-right: 4px;
}
.l_header ul.nav-list-h>li {
  color: var(--color-list);
  line-height: 64px;
}
.l_header ul.nav-list-h>li >a {
  max-height: 64px;
  overflow: hidden;
  color: inherit;
}
.l_header ul.nav-list-h>li >a:active,
.l_header ul.nav-list-h>li >a.active {
  color: #3dd9b6;
}
.l_header ul.nav-list-h>li:hover>a {
  color: var(--color-list-hl);
}
.l_header ul.nav-list-h>li i.music {
  animation: rotate-effect 1.5s linear infinite;
  -webkit-animation: rotate-effect 1.5s linear infinite;
  -khtml-animation: rotate-effect 1.5s linear infinite;
  -moz-animation: rotate-effect 1.5s linear infinite;
  -o-animation: rotate-effect 1.5s linear infinite;
  -ms-animation: rotate-effect 1.5s linear infinite;
}
@-moz-keyframes rotate-effect {
  0% {
    transform: rotate(0);
    -webkit-transform: rotate(0);
    -khtml-transform: rotate(0);
    -moz-transform: rotate(0);
    -o-transform: rotate(0);
    -ms-transform: rotate(0);
  }
  25% {
    transform: rotate(90deg);
    -webkit-transform: rotate(90deg);
    -khtml-transform: rotate(90deg);
    -moz-transform: rotate(90deg);
    -o-transform: rotate(90deg);
    -ms-transform: rotate(90deg);
  }
  50% {
    transform: rotate(180deg);
    -webkit-transform: rotate(180deg);
    -khtml-transform: rotate(180deg);
    -moz-transform: rotate(180deg);
    -o-transform: rotate(180deg);
    -ms-transform: rotate(180deg);
  }
  75% {
    transform: rotate(270deg);
    -webkit-transform: rotate(270deg);
    -khtml-transform: rotate(270deg);
    -moz-transform: rotate(270deg);
    -o-transform: rotate(270deg);
    -ms-transform: rotate(270deg);
  }
  100% {
    transform: rotate(360deg);
    -webkit-transform: rotate(360deg);
    -khtml-transform: rotate(360deg);
    -moz-transform: rotate(360deg);
    -o-transform: rotate(360deg);
    -ms-transform: rotate(360deg);
  }
}
@-webkit-keyframes rotate-effect {
  0% {
    transform: rotate(0);
    -webkit-transform: rotate(0);
    -khtml-transform: rotate(0);
    -moz-transform: rotate(0);
    -o-transform: rotate(0);
    -ms-transform: rotate(0);
  }
  25% {
    transform: rotate(90deg);
    -webkit-transform: rotate(90deg);
    -khtml-transform: rotate(90deg);
    -moz-transform: rotate(90deg);
    -o-transform: rotate(90deg);
    -ms-transform: rotate(90deg);
  }
  50% {
    transform: rotate(180deg);
    -webkit-transform: rotate(180deg);
    -khtml-transform: rotate(180deg);
    -moz-transform: rotate(180deg);
    -o-transform: rotate(180deg);
    -ms-transform: rotate(180deg);
  }
  75% {
    transform: rotate(270deg);
    -webkit-transform: rotate(270deg);
    -khtml-transform: rotate(270deg);
    -moz-transform: rotate(270deg);
    -o-transform: rotate(270deg);
    -ms-transform: rotate(270deg);
  }
  100% {
    transform: rotate(360deg);
    -webkit-transform: rotate(360deg);
    -khtml-transform: rotate(360deg);
    -moz-transform: rotate(360deg);
    -o-transform: rotate(360deg);
    -ms-transform: rotate(360deg);
  }
}
@-o-keyframes rotate-effect {
  0% {
    transform: rotate(0);
    -webkit-transform: rotate(0);
    -khtml-transform: rotate(0);
    -moz-transform: rotate(0);
    -o-transform: rotate(0);
    -ms-transform: rotate(0);
  }
  25% {
    transform: rotate(90deg);
    -webkit-transform: rotate(90deg);
    -khtml-transform: rotate(90deg);
    -moz-transform: rotate(90deg);
    -o-transform: rotate(90deg);
    -ms-transform: rotate(90deg);
  }
  50% {
    transform: rotate(180deg);
    -webkit-transform: rotate(180deg);
    -khtml-transform: rotate(180deg);
    -moz-transform: rotate(180deg);
    -o-transform: rotate(180deg);
    -ms-transform: rotate(180deg);
  }
  75% {
    transform: rotate(270deg);
    -webkit-transform: rotate(270deg);
    -khtml-transform: rotate(270deg);
    -moz-transform: rotate(270deg);
    -o-transform: rotate(270deg);
    -ms-transform: rotate(270deg);
  }
  100% {
    transform: rotate(360deg);
    -webkit-transform: rotate(360deg);
    -khtml-transform: rotate(360deg);
    -moz-transform: rotate(360deg);
    -o-transform: rotate(360deg);
    -ms-transform: rotate(360deg);
  }
}
@keyframes rotate-effect {
  0% {
    transform: rotate(0);
    -webkit-transform: rotate(0);
    -khtml-transform: rotate(0);
    -moz-transform: rotate(0);
    -o-transform: rotate(0);
    -ms-transform: rotate(0);
  }
  25% {
    transform: rotate(90deg);
    -webkit-transform: rotate(90deg);
    -khtml-transform: rotate(90deg);
    -moz-transform: rotate(90deg);
    -o-transform: rotate(90deg);
    -ms-transform: rotate(90deg);
  }
  50% {
    transform: rotate(180deg);
    -webkit-transform: rotate(180deg);
    -khtml-transform: rotate(180deg);
    -moz-transform: rotate(180deg);
    -o-transform: rotate(180deg);
    -ms-transform: rotate(180deg);
  }
  75% {
    transform: rotate(270deg);
    -webkit-transform: rotate(270deg);
    -khtml-transform: rotate(270deg);
    -moz-transform: rotate(270deg);
    -o-transform: rotate(270deg);
    -ms-transform: rotate(270deg);
  }
  100% {
    transform: rotate(360deg);
    -webkit-transform: rotate(360deg);
    -khtml-transform: rotate(360deg);
    -moz-transform: rotate(360deg);
    -o-transform: rotate(360deg);
    -ms-transform: rotate(360deg);
  }
}
.menu-phone li ul.list-v {
  right: calc(100% - 0.5 * 16px);
}
.menu-phone li ul.list-v ul {
  right: calc(100% - 0.5 * 16px);
}
#wrapper {
  max-width: 1080px;
  margin: auto;
}
@media screen and (min-width: 2048px) {
  #wrapper {
    max-width: 55vw;
  }
}
#wrapper .menu {
  -webkit-box-flex: 1;
  -moz-box-flex: 1;
  -webkit-flex: 1 1;
  -ms-flex: 1 1;
  flex: 1 1;
  margin: 0 16px 0 0;
}
#wrapper .menu .list-v ul {
  left: calc(100% - 0.5 * 16px);
}
.menu-phone {
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: none;
  margin-top: 16px;
  right: 8px;
  transition: all 0.28s ease;
  -webkit-transition: all 0.28s ease;
  -khtml-transition: all 0.28s ease;
  -moz-transition: all 0.28s ease;
  -o-transition: all 0.28s ease;
  -ms-transition: all 0.28s ease;
}
.menu-phone ul {
  right: calc(100% - 0.5 * 16px);
}
@media screen and (max-width: 500px) {
  .menu-phone {
    display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
    display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
    display: block;
  }
}
.l_header {
  max-width: 65vw;
  left: calc((100% - 65vw) * 0.5);
  border-bottom-left-radius: 8px;
  border-bottom-right-radius: 8px;
}
@media screen and (max-width: 2048px) {
  .l_header {
    max-width: 1112px;
    left: calc((100% - 1112px) * 0.5);
  }
}
@media screen and (max-width: 1112px) {
  .l_header {
    left: 0;
    border-radius: 0;
    -webkit-border-radius: 0;
    max-width: 100%;
  }
}
@media screen and (max-width: 500px) {
  .l_header .container {
    margin-left: 0;
    margin-right: 0;
  }
  .l_header #wrapper .nav-main .title {
    padding-left: 16px;
    padding-right: 16px;
  }
  .l_header #wrapper .nav-sub {
    width: 100%;
  }
  .l_header #wrapper .nav-sub .title {
    overflow-y: scroll;
    margin-top: 2px;
    padding: 8px 16px;
  }
  .l_header #wrapper .switcher {
    display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
    display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
    display: -ms-flexbox /* TWEENER - IE 10 */;
    display: -webkit-flex /* NEW - Chrome */;
    display: flex /* NEW, Spec - Opera 12.1, Firefox 20+ */;
    display: flex;
    margin-right: 8px;
  }
  .l_header .menu {
    display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
    display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
    display: none;
  }
}
@media screen and (max-width: 500px) {
  .list-v li {
    max-width: 270px;
  }
}
#u-search {
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: none;
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  padding: 60px 20px;
  z-index: 1001;
}
@media screen and (max-width: 680px) {
  #u-search {
    padding: 0px;
  }
}

  </style>
  <link rel="stylesheet" href="/css/style.css" media="print" onload="this.media='all';this.onload=null">
  <noscript><link rel="stylesheet" href="/css/style.css"></noscript>
  
<script>
if (/*@cc_on!@*/false || (!!window.MSInputMethodContext && !!document.documentMode))
    document.write(
	'<style>'+
		'html{'+
			'overflow-x: hidden !important;'+
			'overflow-y: hidden !important;'+
		'}'+
		'.kill-ie{'+
			'text-align:center;'+
			'height: 100%;'+
			'margin-top: 15%;'+
			'margin-bottom: 5500%;'+
		'}'+
    '.kill-t{'+
      'font-size: 2rem;'+
    '}'+
    '.kill-c{'+
      'font-size: 1.2rem;'+
    '}'+
		'#l_header,#l_body{'+
			'display: none;'+
		'}'+
	'</style>'+
    '<div class="kill-ie">'+
        `<span class="kill-t"><b>Sorry, your browser cannot access this site</b></span><br/>`+
        `<span class="kill-c">Microsoft has terminated support for Internet Explorer (IE) 10 and earlier versions in 2016. <br/>There are great security risks to continue using it. Please use contemporary mainstream browsers to access.</span><br/>`+
        `<a target="_blank" rel="noopener" href="https://blogs.windows.com/windowsexperience/2021/05/19/the-future-of-internet-explorer-on-windows-10-is-in-microsoft-edge/"><strong>Learn more ></strong></a>`+
    '</div>');
</script>


<noscript>
	<style>
		html{
			overflow-x: hidden !important;
			overflow-y: hidden !important;
		}
		.kill-noscript{
			text-align:center;
			height: 100%;
			margin-top: 15%;
			margin-bottom: 5500%;
		}
    .kill-t{
      font-size: 2rem;
    }
    .kill-c{
      font-size: 1.2rem;
    }
		#l_header,#l_body{
			display: none;
		}
	</style>
    <div class="kill-noscript">
        <span class="kill-t"><b>Sorry, your browser cannot access this site</b></span><br/>
        <span class="kill-c">This page requires browser support (enable) JavaScript</span><br/>
        <a target="_blank" rel="noopener" href="https://www.baidu.com/s?wd=启用JavaScript"><strong>Learn more ></strong></a>
    </div>
</noscript>


  <script>
  /************这个文件存放不需要重载的全局变量和全局函数*********/
  window.volantis = {}; // volantis 全局变量
  volantis.debug = "env"; // 调试模式
  volantis.dom = {}; // 页面Dom see: /source/js/app.js etc.

  volantis.GLOBAL_CONFIG ={
    debug: "env",
    cdn: {"js":{"app":"/js/app.js","parallax":"/js/plugins/parallax.js","rightMenu":"/js/plugins/rightMenu.js","rightMenus":"/js/plugins/rightMenus.js","sites":"/js/plugins/tags/sites.js","friends":"/js/plugins/tags/friends.js","contributors":"/js/plugins/tags/contributors.js","search":"/js/search/hexo.js"},"css":{"style":"/css/style.css"}},
    default: {"avatar":"https://unpkg.com/volantis-static@0.0.1654736714924/media/placeholder/avatar/round/3442075.svg","link":"https://unpkg.com/volantis-static@0.0.1654736714924/media/placeholder/link/8f277b4ee0ecd.svg","cover":"https://unpkg.com/volantis-static@0.0.1654736714924/media/placeholder/cover/76b86c0226ffd.svg","image":"https://unpkg.com/volantis-static@0.0.1654736714924/media/placeholder/image/2659360.svg"},
    lastupdate: new Date(1708699590610),
    sidebar: {
      for_page: ["blogger","category","tagcloud","donate"],
      for_post: ["toc"],
      webinfo: {
        lastupd: {
          enable: true,
          friendlyShow: true
        },
        runtime: {
          data: "2020/01/01",
          unit: "天"
        }
      }
    },
    plugins: {
      message: {"enable":true,"css":"https://unpkg.com/volantis-static@0.0.1654736714924/libs/izitoast/dist/css/iziToast.min.css","js":"https://unpkg.com/volantis-static@0.0.1654736714924/libs/izitoast/dist/js/iziToast.min.js","icon":{"default":"fa-solid fa-info-circle light-blue","quection":"fa-solid fa-question-circle light-blue"},"time":{"default":5000,"quection":20000},"position":"topRight","transitionIn":"bounceInLeft","transitionOut":"fadeOutRight","titleColor":"var(--color-text)","messageColor":"var(--color-text)","backgroundColor":"var(--color-card)","zindex":2147483647,"copyright":{"enable":true,"title":"知识共享许可协议","message":"请遵守 CC BY-NC-SA 4.0 协议。","icon":"far fa-copyright light-blue"},"aplayer":{"enable":true,"play":"fa-solid fa-play","pause":"fa-solid fa-pause"},"rightmenu":{"enable":true,"notice":true}},
      fancybox: {"css":"https://unpkg.com/volantis-static@0.0.1654736714924/libs/@fancyapps/ui/dist/fancybox.css","js":"https://unpkg.com/volantis-static@0.0.1654736714924/libs/@fancyapps/ui/dist/fancybox.umd.js"},
      
      
      
    }
  }

  /******************** volantis.EventListener ********************************/
  // 事件监听器 see: /source/js/app.js
  volantis.EventListener = {}
  // 这里存放pjax切换页面时将被移除的事件监听器
  volantis.EventListener.list = []
  //构造方法
  function volantisEventListener(type, f, ele) {
    this.type = type
    this.f = f
    this.ele = ele
  }
  // 移除事件监听器
  volantis.EventListener.remove = () => {
    volantis.EventListener.list.forEach(function (i) {
      i.ele.removeEventListener(i.type, i.f, false)
    })
    volantis.EventListener.list = []
  }
  /******************** volantis.dom.$ ********************************/
  // 注：这里没有选择器，也没有forEach一次只处理一个dom，这里重新封装主题常用的dom方法，返回的是dom对象，对象包含了以下方法，同时保留dom的原生API
  function volantisDom(ele) {
    if (!ele) ele = document.createElement("div")
    this.ele = ele;
    // ==============================================================
    this.ele.find = (c) => {
      let q = this.ele.querySelector(c)
      if (q)
        return new volantisDom(q)
    }
    // ==============================================================
    this.ele.hasClass = (c) => {
      return this.ele.className.match(new RegExp('(\\s|^)' + c + '(\\s|$)'));
    }
    this.ele.addClass = (c) => {
      this.ele.classList.add(c);
      return this.ele
    }
    this.ele.removeClass = (c) => {
      this.ele.classList.remove(c);
      return this.ele
    }
    this.ele.toggleClass = (c) => {
      if (this.ele.hasClass(c)) {
        this.ele.removeClass(c)
      } else {
        this.ele.addClass(c)
      }
      return this.ele
    }
    // ==============================================================
    // 参数 r 为 true 表示pjax切换页面时事件监听器将被移除，false不移除
    this.ele.on = (c, f, r = 1) => {
      this.ele.addEventListener(c, f, false)
      if (r) {
        volantis.EventListener.list.push(new volantisEventListener(c, f, this.ele))
      }
      return this.ele
    }
    this.ele.click = (f, r) => {
      this.ele.on("click", f, r)
      return this.ele
    }
    this.ele.scroll = (f, r) => {
      this.ele.on("scroll", f, r)
      return this.ele
    }
    // ==============================================================
    this.ele.html = (c) => {
      // if(c=== undefined){
      //   return this.ele.innerHTML
      // }else{
      this.ele.innerHTML = c
      return this.ele
      // }
    }
    // ==============================================================
    this.ele.hide = (c) => {
      this.ele.style.display = "none"
      return this.ele
    }
    this.ele.show = (c) => {
      this.ele.style.display = "block"
      return this.ele
    }
    // ==============================================================
    return this.ele
  }
  volantis.dom.$ = (ele) => {
    return !!ele ? new volantisDom(ele) : null;
  }
  /******************** RunItem ********************************/
  function RunItem() {
    this.list = []; // 存放回调函数
    this.start = () => {
      for (var i = 0; i < this.list.length; i++) {
        this.list[i].run();
      }
    };
    this.push = (fn, name, setRequestAnimationFrame = true) => {
      let myfn = fn
      if (setRequestAnimationFrame) {
        myfn = ()=>{
          volantis.requestAnimationFrame(fn)
        }
      }
      var f = new Item(myfn, name);
      this.list.push(f);
    };
    this.remove = (name) =>{
      for (let index = 0; index < this.list.length; index++) {
        const e = this.list[index];
        if (e.name == name) {
          this.list.splice(index,1);
        }
      }
    }
    // 构造一个可以run的对象
    function Item(fn, name) {
      // 函数名称
      this.name = name || fn.name;
      // run方法
      this.run = () => {
        try {
          fn()
        } catch (error) {
          console.log(error);
        }
      };
    }
  }
  /******************** Pjax ********************************/
  // /layout/_plugins/pjax/index.ejs
  // volantis.pjax.send(callBack[,"callBackName"]) 传入pjax:send回调函数
  // volantis.pjax.push(callBack[,"callBackName"]) 传入pjax:complete回调函数
  // volantis.pjax.error(callBack[,"callBackName"]) 传入pjax:error回调函数
  volantis.pjax = {};
  volantis.pjax.method = {
    complete: new RunItem(),
    error: new RunItem(),
    send: new RunItem(),
  };
  volantis.pjax = Object.assign(volantis.pjax, {
    push: volantis.pjax.method.complete.push,
    error: volantis.pjax.method.error.push,
    send: volantis.pjax.method.send.push,
  });
  /******************** RightMenu ********************************/
  // volantis.rightmenu.handle(callBack[,"callBackName"]) 外部菜单项控制
  // 可在 volantis.mouseEvent 处获取右键事件
  volantis.rightmenu = {};
  volantis.rightmenu.method = {
    handle: new RunItem(),
  }
  volantis.rightmenu = Object.assign(volantis.rightmenu, {
    handle: volantis.rightmenu.method.handle.push,
  });
  /********************  Dark Mode  ********************************/
  // /layout/_partial/scripts/darkmode.ejs
  // volantis.dark.mode 当前模式 dark or light
  // volantis.dark.toggle() 暗黑模式触发器
  // volantis.dark.push(callBack[,"callBackName"]) 传入触发器回调函数
  volantis.dark = {};
  volantis.dark.method = {
    toggle: new RunItem(),
  };
  volantis.dark = Object.assign(volantis.dark, {
    push: volantis.dark.method.toggle.push,
  });
  /********************  Message  ********************************/
  // VolantisApp.message
  /********************  isMobile  ********************************/
  // /source/js/app.js
  // volantis.isMobile
  // volantis.isMobileOld
  /********************脚本动态加载函数********************************/
  // volantis.js(src, cb)  cb 可以传入onload回调函数 或者 JSON对象 例如: volantis.js("src", ()=>{}) 或 volantis.js("src", {defer:true,onload:()=>{}})
  // volantis.css(src)

  // 返回Promise对象，如下方法同步加载资源，这利于处理文件资源之间的依赖关系，例如：APlayer 需要在 MetingJS 之前加载
  // (async () => {
  //     await volantis.js("...theme.plugins.aplayer.js.aplayer...")
  //     await volantis.js("...theme.plugins.aplayer.js.meting...")
  // })();

  // 已经加入了setTimeout
  volantis.js = (src, cb) => {
    return new Promise(resolve => {
      setTimeout(function () {
        var HEAD = document.getElementsByTagName("head")[0] || document.documentElement;
        var script = document.createElement("script");
        script.setAttribute("type", "text/javascript");
        if (cb) {
          if (JSON.stringify(cb)) {
            for (let p in cb) {
              if (p == "onload") {
                script[p] = () => {
                  cb[p]()
                  resolve()
                }
              } else {
                script[p] = cb[p]
                script.onload = resolve
              }
            }
          } else {
            script.onload = () => {
              cb()
              resolve()
            };
          }
        } else {
          script.onload = resolve
        }
        script.setAttribute("src", src);
        HEAD.appendChild(script);
      });
    });
  }
  volantis.css = (src) => {
    return new Promise(resolve => {
      setTimeout(function () {
        var link = document.createElement('link');
        link.rel = "stylesheet";
        link.href = src;
        link.onload = resolve;
        document.getElementsByTagName("head")[0].appendChild(link);
      });
    });
  }
  /********************按需加载的插件********************************/
  // volantis.import.jQuery().then(()=>{})
  volantis.import = {
    jQuery: () => {
      if (typeof jQuery == "undefined") {
        return volantis.js("https://unpkg.com/volantis-static@0.0.1654736714924/libs/jquery/dist/jquery.min.js")
      } else {
        return new Promise(resolve => {
          resolve()
        });
      }
    }
  }
  /********************** requestAnimationFrame ********************************/
  // 1、requestAnimationFrame 会把每一帧中的所有 DOM 操作集中起来，在一次重绘或回流中就完成，并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率，一般来说，这个频率为每秒60帧。
  // 2、在隐藏或不可见的元素中，requestAnimationFrame 将不会进行重绘或回流，这当然就意味着更少的的 cpu，gpu 和内存使用量。
  volantis.requestAnimationFrame = (fn)=>{
    if (!window.requestAnimationFrame) {
      window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame;
    }
    window.requestAnimationFrame(fn)
  }
  /************************ layoutHelper *****************************************/
  volantis.layoutHelper = (helper, html, opt)=>{
    opt = Object.assign({clean:false, pjax:true}, opt)
    function myhelper(helper, html, clean) {
      volantis.tempDiv = document.createElement("div");
      volantis.tempDiv.innerHTML = html;
      let layoutHelper = document.querySelector("#layoutHelper-"+helper)
      if (layoutHelper) {
        if (clean) {
          layoutHelper.innerHTML = ""
        }
        layoutHelper.append(volantis.tempDiv);
      }
    }
    myhelper(helper, html, opt.clean)
    if (opt.pjax) {
      volantis.pjax.push(()=>{
        myhelper(helper, html, opt.clean)
      },"layoutHelper-"+helper)
    }
  }
  /****************************** 滚动事件处理 ****************************************/
  volantis.scroll = {
    engine: new RunItem(),
    unengine: new RunItem(),
  };
  volantis.scroll = Object.assign(volantis.scroll, {
    push: volantis.scroll.engine.push,
  });
  // 滚动条距离顶部的距离
  volantis.scroll.getScrollTop = () =>{
    let scrollPos;
    if (window.pageYOffset) {
      scrollPos = window.pageYOffset;
    } else if (document.compatMode && document.compatMode != 'BackCompat') {
      scrollPos = document.documentElement.scrollTop;
    } else if (document.body) {
      scrollPos = document.body.scrollTop;
    }
    return scrollPos;
  }
  // 使用 requestAnimationFrame 处理滚动事件
  // `volantis.scroll.del` 中存储了一个数值, 该数值检测一定时间间隔内滚动条滚动的位移, 数值的检测频率是浏览器的刷新频率. 数值为正数时, 表示向下滚动. 数值为负数时, 表示向上滚动.
  volantis.scroll.handleScrollEvents = () => {
    volantis.scroll.lastScrollTop = volantis.scroll.getScrollTop()
    function loop() {
      const scrollTop = volantis.scroll.getScrollTop();
      if (volantis.scroll.lastScrollTop !== scrollTop) {
        volantis.scroll.del = scrollTop - volantis.scroll.lastScrollTop;
        volantis.scroll.lastScrollTop = scrollTop;
        // if (volantis.scroll.del > 0) {
        //   console.log("向下滚动");
        // } else {
        //   console.log("向上滚动");
        // }
        // 注销过期的unengine未滚动事件
        volantis.scroll.unengine.list=[]
        volantis.scroll.engine.start();
      }else{
        volantis.scroll.unengine.start();
      }
      volantis.requestAnimationFrame(loop)
    }
    volantis.requestAnimationFrame(loop)
  }
  volantis.scroll.handleScrollEvents()
  volantis.scroll.ele = null;
  // 触发页面滚动至目标元素位置
  volantis.scroll.to = (ele, option = {}) => {
    if (!ele) return;
    volantis.scroll.ele = ele;
    // 默认配置
    opt = {
      top: ele.getBoundingClientRect().top + document.documentElement.scrollTop,
      behavior: "smooth"
    }
    // 定义配置
    if ("top" in option) {
      opt.top = option.top
    }
    if ("behavior" in option) {
      opt.behavior = option.behavior
    }
    if ("addTop" in option) {
      opt.top += option.addTop
    }
    if (!("observerDic" in option)) {
      option.observerDic = 100
    }
    // 滚动
    window.scrollTo(opt);
    // 监视器
    // 监视并矫正元素滚动到指定位置
    // 用于处理 lazyload 引起的 cls 导致的定位失败问题
    // option.observer = false
    if (option.observer) {
      setTimeout(() => {
        if (volantis.scroll.ele != ele) {
          return
        }
        volantis.scroll.unengine.push(() => {
          let me = ele.getBoundingClientRect().top
          if(!(me >= -option.observerDic && me <= option.observerDic)){
            volantis.scroll.to(ele, option)
          }
          volantis.scroll.unengine.remove("unengineObserver")
        },"unengineObserver")
      },1000)
    }
  }
  /********************** Content Visibility ********************************/
  // 见 source/css/first.styl 如果遇到任何问题 删除 .post-story 即可
  // 一个元素被声明 content-visibility 属性后 如果元素不在 viewport 中 浏览器不会计算其后代元素样式和属性 从而节省 Style & Layout 耗时
  // content-visibility 的副作用: 锚点失效 等等(实验初期 暂不明确), 使用此方法清除样式
  volantis.cleanContentVisibility = ()=>{
    if (document.querySelector(".post-story")) {
      console.log("cleanContentVisibility");
      document.querySelectorAll(".post-story").forEach(e=>{
        e.classList.remove("post-story")
      })
    }
  }
  /******************************************************************************/
  /******************************************************************************/
  /******************************************************************************/
  //图像加载出错时的处理
  function errorImgAvatar(img) {
    img.src = "https://unpkg.com/volantis-static@0.0.1654736714924/media/placeholder/avatar/round/3442075.svg";
    img.onerror = null;
  }
  function errorImgCover(img) {
    img.src = "https://unpkg.com/volantis-static@0.0.1654736714924/media/placeholder/cover/76b86c0226ffd.svg";
    img.onerror = null;
  }
  /******************************************************************************/
</script>

  <!-- import head_end begin -->
  <!-- import head_end end -->
  <!-- Custom Files headEnd begin-->
  
  <!-- Custom Files headEnd end-->
  <!-- front-matter head_end begin -->
  <!-- front-matter head_end end -->
</head>
  <body itemscope itemtype="http://schema.org/WebPage">
    <!-- import body_begin begin-->
    <!-- import body_begin end-->
    <!-- Custom Files bodyBegin begin-->
    
    <!-- Custom Files bodyBegin end-->
    <!-- front-matter body_begin begin -->
    <!-- front-matter body_begin end -->
    <header itemscope itemtype="http://schema.org/WPHeader" id="l_header" class="l_header auto shadow floatable blur show" style='opacity: 0' >
  <div class='container'>
  <div id='wrapper'>
    <div class='nav-sub'>
      <p class="title"></p>
      <ul class='switcher nav-list-h m-phone' id="pjax-header-nav-list">
        <li><a id="s-comment" class="fa-solid fa-comments fa-fw" target="_self"  href="/" onclick="return false;" title="comment"></a></li>
        
          <li><a id="s-toc" class="s-toc fa-solid fa-list fa-fw" target="_self"  href="/" onclick="return false;" title="toc"></a></li>
        
      </ul>
    </div>
		<div class="nav-main">
      
        
        <a class="title flat-box" target="_self" href='/'>
          
            <img no-lazy class='logo' src='https://unpkg.com/volantis-static@0.0.1654736714924/media/org.volantis/blog/Logo-NavBar@3x.png'/>
          
          
          
        </a>
      

			<div class='menu navigation'>
				<ul class='nav-list-h m-pc'>
          
          
          
            
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover"
                href="/" title="博客"
                  
                  
                  
                    active-action="action-home"
                  >
                  <i class='fa-solid fa-rss fa-fw'></i>博客
                </a>
                
              </li>
            
          
          
            
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover"
                href="/categories/" title="分类"
                  
                  
                  
                    active-action="action-categories"
                  >
                  <i class='fa-solid fa-folder-open fa-fw'></i>分类
                </a>
                
              </li>
            
          
          
            
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover"
                href="/tags/" title="标签"
                  
                  
                  
                    active-action="action-tags"
                  >
                  <i class='fa-solid fa-tags fa-fw'></i>标签
                </a>
                
              </li>
            
          
          
            
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover"
                href="/archives/" title="归档"
                  
                  
                  
                    active-action="action-archives"
                  >
                  <i class='fa-solid fa-archive fa-fw'></i>归档
                </a>
                
              </li>
            
          
          
            
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover"
                href="/friends/" title="友链"
                  
                  
                  
                    active-action="action-friends"
                  >
                  <i class='fa-solid fa-link fa-fw'></i>友链
                </a>
                
              </li>
            
          
          
            
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover"
                href="/about/" title="关于"
                  
                  
                  
                    active-action="action-about"
                  >
                  <i class='fa-solid fa-info-circle fa-fw'></i>关于
                </a>
                
              </li>
            
          
          
				</ul>
			</div>
      
      <div class="m_search">
        <form name="searchform" class="form u-search-form">
          <i class="icon fa-solid fa-search fa-fw"></i>
          <input type="text" class="input u-search-input" placeholder="Search..." />
        </form>
      </div>
      

			<ul class='switcher nav-list-h m-phone'>
				
					<li><a class="s-search fa-solid fa-search fa-fw" target="_self" href="/" onclick="return false;" title="search"></a></li>
				
				<li>
          <a class="s-menu fa-solid fa-bars fa-fw" target="_self" href="/" onclick="return false;" title="menu"></a>
          <ul class="menu-phone list-v navigation white-box">
            
              
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover"
                href="/" title="博客"
                  
                  
                  
                    active-action="action-home"
                  >
                  <i class='fa-solid fa-rss fa-fw'></i>博客
                </a>
                
              </li>
            
          
            
              
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover"
                href="/categories/" title="分类"
                  
                  
                  
                    active-action="action-categories"
                  >
                  <i class='fa-solid fa-folder-open fa-fw'></i>分类
                </a>
                
              </li>
            
          
            
              
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover"
                href="/tags/" title="标签"
                  
                  
                  
                    active-action="action-tags"
                  >
                  <i class='fa-solid fa-tags fa-fw'></i>标签
                </a>
                
              </li>
            
          
            
              
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover"
                href="/archives/" title="归档"
                  
                  
                  
                    active-action="action-archives"
                  >
                  <i class='fa-solid fa-archive fa-fw'></i>归档
                </a>
                
              </li>
            
          
            
              
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover"
                href="/friends/" title="友链"
                  
                  
                  
                    active-action="action-friends"
                  >
                  <i class='fa-solid fa-link fa-fw'></i>友链
                </a>
                
              </li>
            
          
            
              
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover"
                href="/about/" title="关于"
                  
                  
                  
                    active-action="action-about"
                  >
                  <i class='fa-solid fa-info-circle fa-fw'></i>关于
                </a>
                
              </li>
            
          
            
          </ul>
        </li>
			</ul>

      <!-- Custom Files header begin -->
      
      <!-- Custom Files header end -->
		</div>
	</div>
  </div>
</header>

    <div id="l_body">
      <div id="l_cover">
  
    
      <!-- see: /layout/_partial/scripts/_ctrl/coverCtrl.ejs -->
      <div id="none" class='cover-wrapper post dock' style="display: none;">
        
  <div class='cover-bg lazyload placeholder' data-bg="https://gcore.jsdelivr.net/gh/MHG-LAB/cron@gh-pages/bing/bing.jpg"></div>

<div class='cover-body'>
  <div class='top'>
    
    
      <p class="title">Volantis</p>
    
    
  </div>
  <div class='bottom'>
    <div class='menu navigation'>
      <div class='list-h'>
        
          
            <a href="/v4/getting-started/"
              
              
              active-action="action-v4getting-started">
              <img src='https://unpkg.com/volantis-static@0.0.1654736714924/media/twemoji/assets/svg/1f5c3.svg'><p>文档</p>
            </a>
          
            <a href="/faqs/"
              
              
              active-action="action-faqs">
              <img src='https://unpkg.com/volantis-static@0.0.1654736714924/media/twemoji/assets/svg/1f516.svg'><p>帮助</p>
            </a>
          
            <a href="/examples/"
              
              
              active-action="action-examples">
              <img src='https://unpkg.com/volantis-static@0.0.1654736714924/media/twemoji/assets/svg/1f396.svg'><p>示例</p>
            </a>
          
            <a href="/contributors/"
              
              
              active-action="action-contributors">
              <img src='https://unpkg.com/volantis-static@0.0.1654736714924/media/twemoji/assets/svg/1f389.svg'><p>社区</p>
            </a>
          
            <a href="/archives/"
              
              
              active-action="action-archives">
              <img src='https://unpkg.com/volantis-static@0.0.1654736714924/media/twemoji/assets/svg/1f4f0.svg'><p>博客</p>
            </a>
          
            <a target="_blank" rel="noopener" href="https://github.com/volantis-x/hexo-theme-volantis/"
              
              
              active-action="action-https:githubcomvolantis-xhexo-theme-volantis">
              <img src='https://unpkg.com/volantis-static@0.0.1654736714924/media/twemoji/assets/svg/1f9ec.svg'><p>源码</p>
            </a>
          
        
      </div>
    </div>
  </div>
</div>

        <div id="scroll-down" style="display: none;"><i class="fa fa-chevron-down scroll-down-effects"></i></div>
      </div>
    
  
</div>

      <div id="safearea">
        <div class="body-wrapper">
          
<div id="l_main" class=''>
  <article itemscope itemtype="http://schema.org/Article" class="article post white-box reveal md shadow floatable blur article-type-post" id="post" itemscope itemprop="blogPost">
  <link itemprop="mainEntityOfPage" href="http://8.219.11.28/2024/02/23/前后端-SpringSecurity/">
  <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
    <meta itemprop="name" content="欢迎来到我的个人博客~">
  </span>
  <span hidden itemprop="post" itemscope itemtype="http://schema.org/Post">
    <meta itemprop="name" content="欢迎来到我的个人博客~">
    <meta itemprop="description" content="这些技术大部分是自己总结的，非官方">
  </span>
  


  
    <span hidden>
      <meta itemprop="image" content="https://unpkg.com/volantis-static@0.0.1654736714924/media/org.volantis/blog/favicon/android-chrome-192x192.png">
    </span>
  
  <div class="article-meta" id="top">
    
    
    
      <span hidden itemprop="name headline">
        
      </span>
    
  </div>


  <div id="layoutHelper-page-plugins"></div>
  <div id="post-body" itemprop="articleBody">
    <h1 id="SpringSecurity"><a href="#SpringSecurity" class="headerlink" title="SpringSecurity"></a>SpringSecurity</h1><h2 id="概念"><a href="#概念" class="headerlink" title="概念"></a>概念</h2><p>Spring家族当中，一个安全管理框架。</p>
<p>Shiro也是一个安全框架，提供了很多安全功能。Shiro比较老，旧的项目当中，可能还在使用。上手还挺简单。</p>
<p>在新项目当中，一线互联网大型项目，都是使用SpringSecurity 。</p>
<h2 id="认证-鉴权"><a href="#认证-鉴权" class="headerlink" title="认证 鉴权"></a>认证 鉴权</h2><p>一般的web项目当中，总会有登陆和鉴权的需求。但是大家一定要区分开。</p>
<ul>
<li><strong>认证</strong>：验证当前访问的用户是不是本系统中的用户。确定是哪一个具体的用户。</li>
<li><strong>鉴权</strong>：经过认证，判断当前登陆用户有没有权限来执行某个操作。</li>
</ul>
<p>所以说，安全框架SpringSecurity 当中，必定会有认证和鉴权的两大核心功能。</p>
<h2 id="入门"><a href="#入门" class="headerlink" title="入门"></a>入门</h2><h3 id="准备web项目"><a href="#准备web项目" class="headerlink" title="准备web项目"></a>准备web项目</h3><h4 id="（1）创建springboot-web项目"><a href="#（1）创建springboot-web项目" class="headerlink" title="#（1）创建springboot web项目"></a><a target="_blank" rel="noopener" href="https://ydlclass.com/doc21xnv/java/third/framework/8%E3%80%81SpringSecurity/#_1-%E5%88%9B%E5%BB%BAspringboot-web%E9%A1%B9%E7%9B%AE">#</a>（1）创建springboot web项目</h4><p>快速构建</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependencies</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-web<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.projectlombok<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>lombok<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">version</span>&gt;</span>1.18.22<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-test<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">scope</span>&gt;</span>test<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependencies</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p>创建controller</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;demo&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DemoController</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;hello&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">hello</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;hello SpringSecurity.  ydlclass.com&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>启动 测试 访问 ：<a target="_blank" rel="noopener" href="http://localhost:8080/demo/hello">http://localhost:8080/demo/helloopen in new window</a></p>
<p><img src="https://ydlclass.com/doc21xnv/assets/image-20220820161603591-7a9326e1.png" class="lazyload" data-srcset="https://ydlclass.com/doc21xnv/assets/image-20220820161603591-7a9326e1.png" srcset="" alt="image-20220820161603591">image-20220820161603591</p>
<h3 id="引入SpringSecurity"><a href="#引入SpringSecurity" class="headerlink" title="引入SpringSecurity"></a>引入SpringSecurity</h3><h4 id="（1）引入依赖"><a href="#（1）引入依赖" class="headerlink" title="#（1）引入依赖"></a><a target="_blank" rel="noopener" href="https://ydlclass.com/doc21xnv/java/third/framework/8%E3%80%81SpringSecurity/#_1-%E5%BC%95%E5%85%A5%E4%BE%9D%E8%B5%96">#</a>（1）引入依赖</h4><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- 引入security起步依赖 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-security<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure>

<h4 id="（2）测试"><a href="#（2）测试" class="headerlink" title="#（2）测试"></a><a target="_blank" rel="noopener" href="https://ydlclass.com/doc21xnv/java/third/framework/8%E3%80%81SpringSecurity/#_2-%E6%B5%8B%E8%AF%95">#</a>（2）测试</h4><p>访问 ： <a target="_blank" rel="noopener" href="http://localhost:8080/demo/hello">http://localhost:8080/demo/helloopen in new window</a></p>
<p>Security 自带的登陆页面</p>
<p><img src="https://ydlclass.com/doc21xnv/assets/image-20220820161941164-cbb43a81.png" class="lazyload" data-srcset="https://ydlclass.com/doc21xnv/assets/image-20220820161941164-cbb43a81.png" srcset="" alt="image-20220820161941164">image-20220820161941164</p>
<p>可以输入自带默认用户名<code>user</code>和 密码(控制台)</p>
<p><img src="https://ydlclass.com/doc21xnv/assets/image-20220820162112307-c9cdee44.png" class="lazyload" data-srcset="https://ydlclass.com/doc21xnv/assets/image-20220820162112307-c9cdee44.png" srcset="" alt="image-20220820162112307">image-20220820162112307</p>
<p>就能访问到数据了</p>
<p><img src="https://ydlclass.com/doc21xnv/assets/image-20220820162139356-78857980.png" class="lazyload" data-srcset="https://ydlclass.com/doc21xnv/assets/image-20220820162139356-78857980.png" srcset="" alt="image-20220820162139356">image-20220820162139356</p>
<h4 id="（3）自带退出"><a href="#（3）自带退出" class="headerlink" title="#（3）自带退出"></a><a target="_blank" rel="noopener" href="https://ydlclass.com/doc21xnv/java/third/framework/8%E3%80%81SpringSecurity/#_3-%E8%87%AA%E5%B8%A6%E9%80%80%E5%87%BA">#</a>（3）自带退出</h4><p><a target="_blank" rel="noopener" href="http://localhost:8080/logout">http://localhost:8080/logoutopen in new window</a></p>
<p><img src="https://ydlclass.com/doc21xnv/assets/image-20220820162224080-61bbcd0e.png" class="lazyload" data-srcset="https://ydlclass.com/doc21xnv/assets/image-20220820162224080-61bbcd0e.png" srcset="" alt="image-20220820162224080">image-20220820162224080</p>
<p><img src="https://ydlclass.com/doc21xnv/assets/image-20220820162245554-51886245.png" class="lazyload" data-srcset="https://ydlclass.com/doc21xnv/assets/image-20220820162245554-51886245.png" srcset="" alt="image-20220820162245554"></p>
<h2 id="认证"><a href="#认证" class="headerlink" title="认证"></a>认证</h2><h3 id="1、web登陆流程"><a href="#1、web登陆流程" class="headerlink" title="1、web登陆流程"></a>1、web登陆流程</h3><p><img src="https://ydlclass.com/doc21xnv/assets/image-20220820185333664-af9918b3.png" class="lazyload" data-srcset="https://ydlclass.com/doc21xnv/assets/image-20220820185333664-af9918b3.png" srcset="" alt="img"></p>
<h4 id="缺点可以改进："><a href="#缺点可以改进：" class="headerlink" title="#缺点可以改进："></a><a target="_blank" rel="noopener" href="https://ydlclass.com/doc21xnv/java/third/framework/8%E3%80%81SpringSecurity/#%E7%BC%BA%E7%82%B9%E5%8F%AF%E4%BB%A5%E6%94%B9%E8%BF%9B">#</a>缺点可以改进：</h4><ol>
<li>现在使用的是security自带的登陆页面，比较丑。 想换成自己项目的，优化的登录页。</li>
<li>用户使用的是security给的用户名和密码。 想真实地去数据库里，tb_user获取真实的用户名和密码。</li>
<li>security自带的cookie\session模式。 想自己生成jwt，无状态登陆。</li>
<li>前端页面怎么携带jwt。 想请求头里带上。</li>
<li>鉴权操作完全没有。 想鉴权做完善。</li>
</ol>
<p>总而言之，自己的一些特定需求，都没有实现。</p>
<h3 id="2、看源码"><a href="#2、看源码" class="headerlink" title="#2、看源码"></a><a target="_blank" rel="noopener" href="https://ydlclass.com/doc21xnv/java/third/framework/8%E3%80%81SpringSecurity/#_2%E3%80%81%E7%9C%8B%E6%BA%90%E7%A0%81">#</a>2、看源码</h3><p>springsecurity 就是通过一些过滤器、拦截器，实现登陆鉴权的流程的。</p>
<h4 id="（1）springsecurity-登陆流程"><a href="#（1）springsecurity-登陆流程" class="headerlink" title="#（1）springsecurity 登陆流程"></a><a target="_blank" rel="noopener" href="https://ydlclass.com/doc21xnv/java/third/framework/8%E3%80%81SpringSecurity/#_1-springsecurity-%E7%99%BB%E9%99%86%E6%B5%81%E7%A8%8B">#</a>（1）springsecurity 登陆流程</h4><p>springsecurity就是一个过滤器链，内置了关于springsecurity的16的过滤器。</p>
<p><img src="https://ydlclass.com/doc21xnv/assets/image-20220820193848980-53da0743.png" class="lazyload" data-srcset="https://ydlclass.com/doc21xnv/assets/image-20220820193848980-53da0743.png" srcset="" alt="image-20220820193848980">image-20220820193848980</p>
<p>注意：我只写出了几个核心过滤器，其他的如下图。</p>
<ul>
<li><strong>UsernamePasswordAuthenticationFilter</strong>：处理我们登陆页面输入的用户名和密码是否正确的过滤器。</li>
<li><strong>ExceptionTranslationFilter</strong>：处理前面的几个过滤器中，有了问题，抛出错误，不让用户登录。</li>
<li><strong>FilterSecurityInterceptor</strong>：经行一个权限校验的拦截器。</li>
</ul>
<p>我们可以找到当前boot项目中的，所有有关security的过滤器链。</p>
<p><img src="https://ydlclass.com/doc21xnv/assets/image-20220820193032051-5e31fbca.png" class="lazyload" data-srcset="https://ydlclass.com/doc21xnv/assets/image-20220820193032051-5e31fbca.png" srcset="" alt="image-20220820193032051">image-20220820193032051</p>
<h4 id="（2）认证流程-再细化！-了解，后期回头看"><a href="#（2）认证流程-再细化！-了解，后期回头看" class="headerlink" title="#（2）认证流程 再细化！ 了解，后期回头看"></a><a target="_blank" rel="noopener" href="https://ydlclass.com/doc21xnv/java/third/framework/8%E3%80%81SpringSecurity/#_2-%E8%AE%A4%E8%AF%81%E6%B5%81%E7%A8%8B-%E5%86%8D%E7%BB%86%E5%8C%96-%E4%BA%86%E8%A7%A3-%E5%90%8E%E6%9C%9F%E5%9B%9E%E5%A4%B4%E7%9C%8B">#</a>（2）认证流程 再细化！ 了解，后期回头看</h4><p>看我如何debug <strong>UsernamePasswordAuthenticationFilter</strong> 运行机制</p>
<p><img src="https://ydlclass.com/doc21xnv/assets/image-20220821175458416-57c8e429.png" class="lazyload" data-srcset="https://ydlclass.com/doc21xnv/assets/image-20220821175458416-57c8e429.png" srcset="" alt="image-20220821175458416">image-20220821175458416</p>
<h3 id="3、自定义登录"><a href="#3、自定义登录" class="headerlink" title="#3、自定义登录"></a><a target="_blank" rel="noopener" href="https://ydlclass.com/doc21xnv/java/third/framework/8%E3%80%81SpringSecurity/#_3%E3%80%81%E8%87%AA%E5%AE%9A%E4%B9%89%E7%99%BB%E5%BD%95">#</a>3、自定义登录</h3><p><img src="https://ydlclass.com/doc21xnv/assets/image-20220822014000886-f37aa999.png" class="lazyload" data-srcset="https://ydlclass.com/doc21xnv/assets/image-20220822014000886-f37aa999.png" srcset="" alt="image-20220822014000886">image-20220822014000886</p>
<h4 id="（1）思路"><a href="#（1）思路" class="headerlink" title="#（1）思路"></a><a target="_blank" rel="noopener" href="https://ydlclass.com/doc21xnv/java/third/framework/8%E3%80%81SpringSecurity/#_1-%E6%80%9D%E8%B7%AF">#</a>（1）思路</h4><p>登陆：</p>
<p> 1自定义登录接口</p>
<p> 调用prodivermanager auth方法</p>
<p> 登陆成功生成jwt</p>
<p> 存入redis</p>
<p> 2自定义userdetailsmanager实现类</p>
<p> 从数据库中获取系统用户</p>
<p>访问资源：自定义认证过滤器</p>
<p> 获取token</p>
<p> 从token中获取userid</p>
<p> 从redis中通过userid获取用户信息</p>
<p> 存SecurityContextHolder</p>
<h4 id="（2）-JWT简介"><a href="#（2）-JWT简介" class="headerlink" title="#（2） JWT简介"></a><a target="_blank" rel="noopener" href="https://ydlclass.com/doc21xnv/java/third/framework/8%E3%80%81SpringSecurity/#_2-jwt%E7%AE%80%E4%BB%8B">#</a>（2） JWT简介</h4><h5 id="a-概念"><a href="#a-概念" class="headerlink" title="#a.概念"></a><a target="_blank" rel="noopener" href="https://ydlclass.com/doc21xnv/java/third/framework/8%E3%80%81SpringSecurity/#a-%E6%A6%82%E5%BF%B5">#</a>a.概念</h5><p>JSON Web Token（JWT）是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。无状态。</p>
<p>好处：不需要服务器端 存session。</p>
<p>特点：可以呗看到，但是不能篡改，因为第三部分用了秘钥。</p>
<p><strong>一个JWT实际上就是一个字符串，它由三部分组成，头部、载荷与签名。</strong> asdf.asdf.asdf</p>
<p><strong>头部（Header）</strong></p>
<p>头部用于描述关于该JWT的最基本的信息，例如其类型以及签名所用的算法等。这也可以被表示成一个JSON对象。</p>
<figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123;&quot;typ&quot;:&quot;JWT&quot;,&quot;alg&quot;:&quot;HS256&quot;&#125;</span><br></pre></td></tr></table></figure>

<p>在头部指明了签名算法是HS256算法。 我们进行BASE64编码<a target="_blank" rel="noopener" href="https://base64.us/%EF%BC%8C%E7%BC%96%E7%A0%81%E5%90%8E%E7%9A%84%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%A6%82%E4%B8%8B%EF%BC%9A">https://base64.us/，编码后的字符串如下：open in new window</a></p>
<figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9</span><br></pre></td></tr></table></figure>

<p><strong>载荷（playload）</strong></p>
<p>载荷就是存放有效信息的地方。</p>
<p>定义一个payload:</p>
<figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123;&quot;sub&quot;:&quot;1234567890&quot;,&quot;name&quot;:&quot;itlils&quot;,&quot;admin&quot;:true,&quot;age&quot;:18&#125;</span><br></pre></td></tr></table></figure>

<p>然后将其进行base64加密，得到Jwt的第二部分。</p>
<figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Iml0bGlscyIsImFkbWluIjp0cnVlLCJhZ2UiOjE4fQ==</span><br></pre></td></tr></table></figure>

<p><strong>签证（signature）</strong></p>
<p>jwt的第三部分是一个签证信息，这个签证信息由三部分组成：</p>
<blockquote>
<p>header (base64后的)</p>
<p>payload (base64后的)</p>
<p>secret</p>
</blockquote>
<p>这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串，然后通过header中声明的加密方式进行加盐secret组合加密，然后就构成了jwt的第三部分。</p>
<figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hs256(&quot;eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Iml0bGlscyIsImFkbWluIjp0cnVlLCJhZ2UiOjE4fQ==&quot;,secret)</span><br></pre></td></tr></table></figure>

<p>将这三部分用.连接成一个完整的字符串,构成了最终的jwt:</p>
<figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">JTdCJTIydHlwJTIyJTNBJTIySldUJTIyJTJDJTIyYWxnJTIyJTNBJTIySFMyNTYlMjIlN0Q=.JTdCJTIyc3ViJTIyJTNBJTIyMTIzNDU2Nzg5MCUyMiUyQyUyMm5hbWUlMjIlM0ElMjJqYWNrJTIyJTJDJTIyYWRtaW4lMjIlM0F0cnVlJTdE.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ</span><br></pre></td></tr></table></figure>

<h5 id="b-JJWT签发与验证token"><a href="#b-JJWT签发与验证token" class="headerlink" title="#b. JJWT签发与验证token"></a><a target="_blank" rel="noopener" href="https://ydlclass.com/doc21xnv/java/third/framework/8%E3%80%81SpringSecurity/#b-jjwt%E7%AD%BE%E5%8F%91%E4%B8%8E%E9%AA%8C%E8%AF%81token">#</a>b. JJWT签发与验证token</h5><p>JJWT是一个提供端到端的JWT创建和验证的Java库。永远免费和开源(Apache License，版本2.0)，JJWT很容易使用和理解。它被设计成一个以建筑为中心的流畅界面，隐藏了它的大部分复杂性。</p>
<p>官方文档：</p>
<p><a target="_blank" rel="noopener" href="https://github.com/jwtk/jjwt">https://github.com/jwtk/jjwtopen in new window</a></p>
<h5 id="c-创建token"><a href="#c-创建token" class="headerlink" title="#c. 创建token"></a><a target="_blank" rel="noopener" href="https://ydlclass.com/doc21xnv/java/third/framework/8%E3%80%81SpringSecurity/#c-%E5%88%9B%E5%BB%BAtoken">#</a>c. 创建token</h5><p>（1）新建项目jwtTest中的pom.xml中添加依赖：</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>io.jsonwebtoken<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>jjwt<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>0.9.0<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p>(2) 创建测试类，代码如下</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">JwtBuilder</span> <span class="variable">jwtBuilder</span> <span class="operator">=</span> Jwts.builder()</span><br><span class="line">                .setId(<span class="string">&quot;666&quot;</span>)<span class="comment">//设置id</span></span><br><span class="line">                .setSubject(<span class="string">&quot;testJwt&quot;</span>)<span class="comment">//主题</span></span><br><span class="line">                .setIssuedAt(<span class="keyword">new</span> <span class="title class_">Date</span>())<span class="comment">//签发日期</span></span><br><span class="line">                .signWith(SignatureAlgorithm.HS256, <span class="string">&quot;itlils&quot;</span>);</span><br><span class="line">        <span class="type">String</span> <span class="variable">jwt</span> <span class="operator">=</span> jwtBuilder.compact();</span><br><span class="line">        System.out.println(jwt);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>运行打印结果：</p>
<figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI2NjYiLCJzdWIiOiJ0ZXN0Snd0IiwiaWF0IjoxNjYxMDc3MjIxfQ.MJBEuwatBTCUNRw8UslNRzCG1obGUly0rQKMXA0XvFA</span><br></pre></td></tr></table></figure>

<p>再次运行，会发现每次运行的结果是不一样的，因为我们的载荷中包含了时间。</p>
<p>验证base64 <a target="_blank" rel="noopener" href="https://tool.oschina.net/encrypt?type=3">https://tool.oschina.net/encrypt?type=3open in new window</a></p>
<h5 id="d-解析token"><a href="#d-解析token" class="headerlink" title="#d.解析token"></a><a target="_blank" rel="noopener" href="https://ydlclass.com/doc21xnv/java/third/framework/8%E3%80%81SpringSecurity/#d-%E8%A7%A3%E6%9E%90token">#</a>d.解析token</h5><p>我们刚才已经创建了token ，在web应用中这个操作是由服务端进行然后发给客户端，客户端在下次向服务端发送请求时需要携带这个token（这就好像是拿着一张门票一样），那服务端接到这个token 应该解析出token中的信息（例如用户id）,根据这些信息查询数据库返回相应的结果。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">String compactJwt=<span class="string">&quot;eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI2NjYiLCJzdWIiOiJ0ZXN0Snd0IiwiaWF0IjoxNjYxMDc3MjIxfQ.MJBEuwatBTCUNRw8UslNRzCG1obGUly0rQKMXA0XvFA&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="type">Claims</span> <span class="variable">claims</span> <span class="operator">=</span> Jwts.parser().setSigningKey(<span class="string">&quot;itlils&quot;</span>).parseClaimsJws(compactJwt).getBody();</span><br><span class="line"></span><br><span class="line">System.out.println(claims);</span><br></pre></td></tr></table></figure>

<p>运行打印效果：</p>
<figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123;jti=666, sub=testJwt, iat=1661077221&#125;</span><br></pre></td></tr></table></figure>

<p>试着将token或签名秘钥篡改一下，会发现运行时就会报错，所以解析token也就是验证token.</p>
<p><strong>注意</strong>：设置签名key必须和生成时一致。</p>
<h5 id="e-设置过期时间"><a href="#e-设置过期时间" class="headerlink" title="#e. 设置过期时间"></a><a target="_blank" rel="noopener" href="https://ydlclass.com/doc21xnv/java/third/framework/8%E3%80%81SpringSecurity/#e-%E8%AE%BE%E7%BD%AE%E8%BF%87%E6%9C%9F%E6%97%B6%E9%97%B4">#</a>e. 设置过期时间</h5><p>有很多时候，我们并不希望签发的token是永久生效的，所以我们可以为token添加一个过期时间。</p>
<p>（1）创建token 并设置过期时间</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line"></span><br><span class="line">                <span class="comment">//当前时间</span></span><br><span class="line">                <span class="type">long</span> <span class="variable">l</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">                System.out.println(l);</span><br><span class="line">                Date date=<span class="keyword">new</span> <span class="title class_">Date</span>(l+<span class="number">10000</span>);</span><br><span class="line"></span><br><span class="line">                <span class="type">JwtBuilder</span> <span class="variable">jwtBuilder</span> <span class="operator">=</span> Jwts.builder()</span><br><span class="line">                        .setId(<span class="string">&quot;666&quot;</span>)<span class="comment">//设置id</span></span><br><span class="line">                        .setSubject(<span class="string">&quot;testJwt&quot;</span>)<span class="comment">//主题</span></span><br><span class="line">                        .setIssuedAt(<span class="keyword">new</span> <span class="title class_">Date</span>())<span class="comment">//签发日期</span></span><br><span class="line">                        .setExpiration(date)<span class="comment">//过期时间</span></span><br><span class="line">                        .signWith(SignatureAlgorithm.HS256, <span class="string">&quot;itlils&quot;</span>);</span><br><span class="line">                <span class="type">String</span> <span class="variable">jwt</span> <span class="operator">=</span> jwtBuilder.compact();</span><br><span class="line">                System.out.println(jwt);</span><br><span class="line"></span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                        Thread.sleep(<span class="number">15000</span>);</span><br><span class="line">                &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(e);</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                <span class="type">Claims</span> <span class="variable">itlils</span> <span class="operator">=</span> Jwts.parser().setSigningKey(<span class="string">&quot;itlils&quot;</span>).parseClaimsJws(jwt).getBody();</span><br><span class="line">                System.out.println(itlils);</span><br><span class="line">        &#125;</span><br></pre></td></tr></table></figure>

<p>解释：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">.setExpiration(date)<span class="comment">//用于设置过期时间 ，参数为Date类型数据</span></span><br></pre></td></tr></table></figure>

<p>运行，打印效果如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE1NTc5MDUzMDgsImV4cCI6MTU1NzkwNTMwOH0.4q5AaTyBRf8SB9B3Tl-I53PrILGyicJC3fgR3gWbvUI</span><br></pre></td></tr></table></figure>

<p>（2）解析TOKEN</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">String compactJwt=<span class="string">&quot;eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE1NTc5MDUzMDgsImV4cCI6MTU1NzkwNTMwOH0.4q5AaTyBRf8SB9B3Tl-I53PrILGyicJC3fgR3gWbvUI&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="type">Claims</span> <span class="variable">claims</span> <span class="operator">=</span> Jwts.parser().setSigningKey(<span class="string">&quot;ydlershe&quot;</span>).parseClaimsJws(compactJwt).getBody();</span><br><span class="line"></span><br><span class="line">System.out.println(claims);</span><br></pre></td></tr></table></figure>

<p>打印效果：</p>
<p><img src="https://ydlclass.com/doc21xnv/assets/image-20220821182626287-32e4bd92.png" class="lazyload" data-srcset="https://ydlclass.com/doc21xnv/assets/image-20220821182626287-32e4bd92.png" srcset="" alt="image-20220821182626287">image-20220821182626287</p>
<p>当前时间超过过期时间，则会报错。</p>
<h5 id="f-自定义claims"><a href="#f-自定义claims" class="headerlink" title="#f.自定义claims"></a><a target="_blank" rel="noopener" href="https://ydlclass.com/doc21xnv/java/third/framework/8%E3%80%81SpringSecurity/#f-%E8%87%AA%E5%AE%9A%E4%B9%89claims">#</a>f.自定义claims</h5><p>我们刚才的例子只是存储了id和subject两个信息，如果你想存储更多的信息（例如角色）可以定义自定义claims。</p>
<p>创建测试类，并设置测试方法：</p>
<p>创建token:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line">        <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line"></span><br><span class="line">                <span class="comment">//当前时间</span></span><br><span class="line">                <span class="type">long</span> <span class="variable">l</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">                System.out.println(l);</span><br><span class="line">                Date date=<span class="keyword">new</span> <span class="title class_">Date</span>(l+<span class="number">10000</span>);</span><br><span class="line"></span><br><span class="line">                <span class="type">JwtBuilder</span> <span class="variable">jwtBuilder</span> <span class="operator">=</span> Jwts.builder()</span><br><span class="line">                        .setId(<span class="string">&quot;666&quot;</span>)<span class="comment">//设置id</span></span><br><span class="line">                        .setSubject(<span class="string">&quot;testJwt&quot;</span>)<span class="comment">//主题</span></span><br><span class="line">                        .setIssuedAt(<span class="keyword">new</span> <span class="title class_">Date</span>())<span class="comment">//签发日期</span></span><br><span class="line">                        .claim(<span class="string">&quot;userId&quot;</span>,<span class="string">&quot;123&quot;</span>)</span><br><span class="line"><span class="comment">//                        .setExpiration(date)//过期时间</span></span><br><span class="line">                        .signWith(SignatureAlgorithm.HS256, <span class="string">&quot;itlils&quot;</span>);</span><br><span class="line">                <span class="type">String</span> <span class="variable">jwt</span> <span class="operator">=</span> jwtBuilder.compact();</span><br><span class="line">                System.out.println(jwt);</span><br><span class="line"></span><br><span class="line"><span class="comment">//                try &#123;</span></span><br><span class="line"><span class="comment">//                        Thread.sleep(15000);</span></span><br><span class="line"><span class="comment">//                &#125; catch (InterruptedException e) &#123;</span></span><br><span class="line"><span class="comment">//                        throw new RuntimeException(e);</span></span><br><span class="line"><span class="comment">//                &#125;</span></span><br><span class="line"></span><br><span class="line">                <span class="type">Claims</span> <span class="variable">itlils</span> <span class="operator">=</span> Jwts.parser().setSigningKey(<span class="string">&quot;itlils&quot;</span>).parseClaimsJws(jwt).getBody();</span><br><span class="line">                System.out.println(itlils);</span><br><span class="line">        &#125;</span><br></pre></td></tr></table></figure>

<p>运行效果：</p>
<p><img src="https://ydlclass.com/doc21xnv/assets/image-20220821182904798-166f9580.png" class="lazyload" data-srcset="https://ydlclass.com/doc21xnv/assets/image-20220821182904798-166f9580.png" srcset="" alt="image-20220821182904798">image-20220821182904798</p>
<h4 id="3-准备新项目"><a href="#3-准备新项目" class="headerlink" title="#(3) 准备新项目"></a><a target="_blank" rel="noopener" href="https://ydlclass.com/doc21xnv/java/third/framework/8%E3%80%81SpringSecurity/#_3-%E5%87%86%E5%A4%87%E6%96%B0%E9%A1%B9%E7%9B%AE">#</a>(3) 准备新项目</h4><p>AuthDemo</p>
<p>①添加依赖</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!--redis依赖--&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-data-redis<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"><span class="comment">&lt;!--fastjson依赖--&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.alibaba<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>fastjson<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>1.2.33<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"><span class="comment">&lt;!--jwt依赖--&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>io.jsonwebtoken<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>jjwt<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>0.9.0<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p>② 添加Redis相关配置</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> com.alibaba.fastjson.JSON;</span><br><span class="line"><span class="keyword">import</span> com.alibaba.fastjson.serializer.SerializerFeature;</span><br><span class="line"><span class="keyword">import</span> com.fasterxml.jackson.databind.JavaType;</span><br><span class="line"><span class="keyword">import</span> com.fasterxml.jackson.databind.ObjectMapper;</span><br><span class="line"><span class="keyword">import</span> com.fasterxml.jackson.databind.type.TypeFactory;</span><br><span class="line"><span class="keyword">import</span> org.springframework.data.redis.serializer.RedisSerializer;</span><br><span class="line"><span class="keyword">import</span> org.springframework.data.redis.serializer.SerializationException;</span><br><span class="line"><span class="keyword">import</span> com.alibaba.fastjson.parser.ParserConfig;</span><br><span class="line"><span class="keyword">import</span> org.springframework.util.Assert;</span><br><span class="line"><span class="keyword">import</span> java.nio.charset.Charset;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Redis使用FastJson序列化</span></span><br><span class="line"><span class="comment"> * </span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> itlils</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">FastJsonRedisSerializer</span>&lt;T&gt; <span class="keyword">implements</span> <span class="title class_">RedisSerializer</span>&lt;T&gt;</span><br><span class="line">&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Charset</span> <span class="variable">DEFAULT_CHARSET</span> <span class="operator">=</span> Charset.forName(<span class="string">&quot;UTF-8&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> Class&lt;T&gt; clazz;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span></span><br><span class="line">    &#123;</span><br><span class="line">        ParserConfig.getGlobalInstance().setAutoTypeSupport(<span class="literal">true</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">FastJsonRedisSerializer</span><span class="params">(Class&lt;T&gt; clazz)</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="built_in">super</span>();</span><br><span class="line">        <span class="built_in">this</span>.clazz = clazz;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">byte</span>[] serialize(T t) <span class="keyword">throws</span> SerializationException</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span> (t == <span class="literal">null</span>)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">byte</span>[<span class="number">0</span>];</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> T <span class="title function_">deserialize</span><span class="params">(<span class="type">byte</span>[] bytes)</span> <span class="keyword">throws</span> SerializationException</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span> (bytes == <span class="literal">null</span> || bytes.length &lt;= <span class="number">0</span>)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">String</span> <span class="variable">str</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">String</span>(bytes, DEFAULT_CHARSET);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> JSON.parseObject(str, clazz);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="keyword">protected</span> JavaType <span class="title function_">getJavaType</span><span class="params">(Class&lt;?&gt; clazz)</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">return</span> TypeFactory.defaultInstance().constructType(clazz);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">import</span> org.springframework.context.annotation.Bean;</span><br><span class="line"><span class="keyword">import</span> org.springframework.context.annotation.Configuration;</span><br><span class="line"><span class="keyword">import</span> org.springframework.data.redis.connection.RedisConnectionFactory;</span><br><span class="line"><span class="keyword">import</span> org.springframework.data.redis.core.RedisTemplate;</span><br><span class="line"><span class="keyword">import</span> org.springframework.data.redis.serializer.StringRedisSerializer;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RedisConfig</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="meta">@SuppressWarnings(value = &#123; &quot;unchecked&quot;, &quot;rawtypes&quot; &#125;)</span></span><br><span class="line">    <span class="keyword">public</span> RedisTemplate&lt;Object, Object&gt; <span class="title function_">redisTemplate</span><span class="params">(RedisConnectionFactory connectionFactory)</span></span><br><span class="line">    &#123;</span><br><span class="line">        RedisTemplate&lt;Object, Object&gt; template = <span class="keyword">new</span> <span class="title class_">RedisTemplate</span>&lt;&gt;();</span><br><span class="line">        template.setConnectionFactory(connectionFactory);</span><br><span class="line"></span><br><span class="line">        <span class="type">FastJsonRedisSerializer</span> <span class="variable">serializer</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FastJsonRedisSerializer</span>(Object.class);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 使用StringRedisSerializer来序列化和反序列化redis的key值</span></span><br><span class="line">        template.setKeySerializer(<span class="keyword">new</span> <span class="title class_">StringRedisSerializer</span>());</span><br><span class="line">        template.setValueSerializer(serializer);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Hash的key也采用StringRedisSerializer的序列化方式</span></span><br><span class="line">        template.setHashKeySerializer(<span class="keyword">new</span> <span class="title class_">StringRedisSerializer</span>());</span><br><span class="line">        template.setHashValueSerializer(serializer);</span><br><span class="line"></span><br><span class="line">        template.afterPropertiesSet();</span><br><span class="line">        <span class="keyword">return</span> template;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>③ 响应类</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> com.fasterxml.jackson.annotation.JsonInclude;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@JsonInclude(JsonInclude.Include.NON_NULL)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ResponseResult</span>&lt;T&gt; &#123;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 状态码</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> Integer code;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 提示信息，如果有错误时，前端可以获取该字段进行提示</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> String msg;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 查询到的结果数据，</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> T data;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">ResponseResult</span><span class="params">(Integer code, String msg)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.code = code;</span><br><span class="line">        <span class="built_in">this</span>.msg = msg;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">ResponseResult</span><span class="params">(Integer code, T data)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.code = code;</span><br><span class="line">        <span class="built_in">this</span>.data = data;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> Integer <span class="title function_">getCode</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> code;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setCode</span><span class="params">(Integer code)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.code = code;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getMsg</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> msg;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setMsg</span><span class="params">(String msg)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.msg = msg;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> T <span class="title function_">getData</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> data;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setData</span><span class="params">(T data)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.data = data;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">ResponseResult</span><span class="params">(Integer code, String msg, T data)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.code = code;</span><br><span class="line">        <span class="built_in">this</span>.msg = msg;</span><br><span class="line">        <span class="built_in">this</span>.data = data;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>④工具类</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br><span class="line">247</span><br><span class="line">248</span><br><span class="line">249</span><br><span class="line">250</span><br><span class="line">251</span><br><span class="line">252</span><br><span class="line">253</span><br><span class="line">254</span><br><span class="line">255</span><br><span class="line">256</span><br><span class="line">257</span><br><span class="line">258</span><br><span class="line">259</span><br><span class="line">260</span><br><span class="line">261</span><br><span class="line">262</span><br><span class="line">263</span><br><span class="line">264</span><br><span class="line">265</span><br><span class="line">266</span><br><span class="line">267</span><br><span class="line">268</span><br><span class="line">269</span><br><span class="line">270</span><br><span class="line">271</span><br><span class="line">272</span><br><span class="line">273</span><br><span class="line">274</span><br><span class="line">275</span><br><span class="line">276</span><br><span class="line">277</span><br><span class="line">278</span><br><span class="line">279</span><br><span class="line">280</span><br><span class="line">281</span><br><span class="line">282</span><br><span class="line">283</span><br><span class="line">284</span><br><span class="line">285</span><br><span class="line">286</span><br><span class="line">287</span><br><span class="line">288</span><br><span class="line">289</span><br><span class="line">290</span><br><span class="line">291</span><br><span class="line">292</span><br><span class="line">293</span><br><span class="line">294</span><br><span class="line">295</span><br><span class="line">296</span><br><span class="line">297</span><br><span class="line">298</span><br><span class="line">299</span><br><span class="line">300</span><br><span class="line">301</span><br><span class="line">302</span><br><span class="line">303</span><br><span class="line">304</span><br><span class="line">305</span><br><span class="line">306</span><br><span class="line">307</span><br><span class="line">308</span><br><span class="line">309</span><br><span class="line">310</span><br><span class="line">311</span><br><span class="line">312</span><br><span class="line">313</span><br><span class="line">314</span><br><span class="line">315</span><br><span class="line">316</span><br><span class="line">317</span><br><span class="line">318</span><br><span class="line">319</span><br><span class="line">320</span><br><span class="line">321</span><br><span class="line">322</span><br><span class="line">323</span><br><span class="line">324</span><br><span class="line">325</span><br><span class="line">326</span><br><span class="line">327</span><br><span class="line">328</span><br><span class="line">329</span><br><span class="line">330</span><br><span class="line">331</span><br><span class="line">332</span><br><span class="line">333</span><br><span class="line">334</span><br><span class="line">335</span><br><span class="line">336</span><br><span class="line">337</span><br><span class="line">338</span><br><span class="line">339</span><br><span class="line">340</span><br><span class="line">341</span><br><span class="line">342</span><br><span class="line">343</span><br><span class="line">344</span><br><span class="line">345</span><br><span class="line">346</span><br><span class="line">347</span><br><span class="line">348</span><br><span class="line">349</span><br><span class="line">350</span><br><span class="line">351</span><br><span class="line">352</span><br><span class="line">353</span><br><span class="line">354</span><br><span class="line">355</span><br><span class="line">356</span><br><span class="line">357</span><br><span class="line">358</span><br><span class="line">359</span><br><span class="line">360</span><br><span class="line">361</span><br><span class="line">362</span><br><span class="line">363</span><br><span class="line">364</span><br><span class="line">365</span><br><span class="line">366</span><br><span class="line">367</span><br><span class="line">368</span><br><span class="line">369</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> io.jsonwebtoken.Claims;</span><br><span class="line"><span class="keyword">import</span> io.jsonwebtoken.JwtBuilder;</span><br><span class="line"><span class="keyword">import</span> io.jsonwebtoken.Jwts;</span><br><span class="line"><span class="keyword">import</span> io.jsonwebtoken.SignatureAlgorithm;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> javax.crypto.SecretKey;</span><br><span class="line"><span class="keyword">import</span> javax.crypto.spec.SecretKeySpec;</span><br><span class="line"><span class="keyword">import</span> java.util.Base64;</span><br><span class="line"><span class="keyword">import</span> java.util.Date;</span><br><span class="line"><span class="keyword">import</span> java.util.UUID;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * JWT工具类</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">JwtUtil</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//有效期为</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Long</span> <span class="variable">JWT_TTL</span> <span class="operator">=</span> <span class="number">60</span> * <span class="number">60</span> *<span class="number">1000L</span>;<span class="comment">// 60 * 60 *1000  一个小时</span></span><br><span class="line">    <span class="comment">//设置秘钥明文</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">JWT_KEY</span> <span class="operator">=</span> <span class="string">&quot;itlils&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> String <span class="title function_">getUUID</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">token</span> <span class="operator">=</span> UUID.randomUUID().toString().replaceAll(<span class="string">&quot;-&quot;</span>, <span class="string">&quot;&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> token;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 生成jtw</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> subject token中要存放的数据（json格式）</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> String <span class="title function_">createJWT</span><span class="params">(String subject)</span> &#123;</span><br><span class="line">        <span class="type">JwtBuilder</span> <span class="variable">builder</span> <span class="operator">=</span> getJwtBuilder(subject, <span class="literal">null</span>, getUUID());<span class="comment">// 设置过期时间</span></span><br><span class="line">        <span class="keyword">return</span> builder.compact();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 生成jtw</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> subject token中要存放的数据（json格式）</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> ttlMillis token超时时间</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> String <span class="title function_">createJWT</span><span class="params">(String subject, Long ttlMillis)</span> &#123;</span><br><span class="line">        <span class="type">JwtBuilder</span> <span class="variable">builder</span> <span class="operator">=</span> getJwtBuilder(subject, ttlMillis, getUUID());<span class="comment">// 设置过期时间</span></span><br><span class="line">        <span class="keyword">return</span> builder.compact();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> JwtBuilder <span class="title function_">getJwtBuilder</span><span class="params">(String subject, Long ttlMillis, String uuid)</span> &#123;</span><br><span class="line">        <span class="type">SignatureAlgorithm</span> <span class="variable">signatureAlgorithm</span> <span class="operator">=</span> SignatureAlgorithm.HS256;</span><br><span class="line">        <span class="type">SecretKey</span> <span class="variable">secretKey</span> <span class="operator">=</span> generalKey();</span><br><span class="line">        <span class="type">long</span> <span class="variable">nowMillis</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">        <span class="type">Date</span> <span class="variable">now</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Date</span>(nowMillis);</span><br><span class="line">        <span class="keyword">if</span>(ttlMillis==<span class="literal">null</span>)&#123;</span><br><span class="line">            ttlMillis=JwtUtil.JWT_TTL;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">long</span> <span class="variable">expMillis</span> <span class="operator">=</span> nowMillis + ttlMillis;</span><br><span class="line">        <span class="type">Date</span> <span class="variable">expDate</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Date</span>(expMillis);</span><br><span class="line">        <span class="keyword">return</span> Jwts.builder()</span><br><span class="line">                .setId(uuid)              <span class="comment">//唯一的ID</span></span><br><span class="line">                .setSubject(subject)   <span class="comment">// 主题  可以是JSON数据</span></span><br><span class="line">                .setIssuer(<span class="string">&quot;ydlclass&quot;</span>)     <span class="comment">// 签发者</span></span><br><span class="line">                .setIssuedAt(now)      <span class="comment">// 签发时间</span></span><br><span class="line">                .signWith(signatureAlgorithm, secretKey) <span class="comment">//使用HS256对称加密算法签名, 第二个参数为秘钥</span></span><br><span class="line">                .setExpiration(expDate);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 创建token</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> id</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> subject</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> ttlMillis</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> String <span class="title function_">createJWT</span><span class="params">(String id, String subject, Long ttlMillis)</span> &#123;</span><br><span class="line">        <span class="type">JwtBuilder</span> <span class="variable">builder</span> <span class="operator">=</span> getJwtBuilder(subject, ttlMillis, id);<span class="comment">// 设置过期时间</span></span><br><span class="line">        <span class="keyword">return</span> builder.compact();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 生成加密后的秘钥 secretKey</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> SecretKey <span class="title function_">generalKey</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">byte</span>[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);</span><br><span class="line">        <span class="type">SecretKey</span> <span class="variable">key</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SecretKeySpec</span>(encodedKey, <span class="number">0</span>, encodedKey.length, <span class="string">&quot;AES&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> key;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 解析</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> jwt</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment">     * <span class="doctag">@throws</span> Exception</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> Claims <span class="title function_">parseJWT</span><span class="params">(String jwt)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="type">SecretKey</span> <span class="variable">secretKey</span> <span class="operator">=</span> generalKey();</span><br><span class="line">        <span class="keyword">return</span> Jwts.parser()</span><br><span class="line">                .setSigningKey(secretKey)</span><br><span class="line">                .parseClaimsJws(jwt)</span><br><span class="line">                .getBody();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">package</span> com.ydlclass.authdemo.utils;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.springframework.beans.factory.annotation.Autowired;</span><br><span class="line"><span class="keyword">import</span> org.springframework.data.redis.core.BoundSetOperations;</span><br><span class="line"><span class="keyword">import</span> org.springframework.data.redis.core.HashOperations;</span><br><span class="line"><span class="keyword">import</span> org.springframework.data.redis.core.RedisTemplate;</span><br><span class="line"><span class="keyword">import</span> org.springframework.data.redis.core.ValueOperations;</span><br><span class="line"><span class="keyword">import</span> org.springframework.stereotype.Component;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.util.*;</span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.TimeUnit;</span><br><span class="line"></span><br><span class="line"><span class="meta">@SuppressWarnings(value = &#123; &quot;unchecked&quot;, &quot;rawtypes&quot; &#125;)</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RedisCache</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">public</span> RedisTemplate redisTemplate;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 缓存基本的对象，Integer、String、实体类等</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> key 缓存的键值</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> value 缓存的值</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> &lt;T&gt; <span class="keyword">void</span> <span class="title function_">setCacheObject</span><span class="params">(<span class="keyword">final</span> String key, <span class="keyword">final</span> T value)</span></span><br><span class="line">    &#123;</span><br><span class="line">        redisTemplate.opsForValue().set(key, value);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 缓存基本的对象，Integer、String、实体类等</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> key 缓存的键值</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> value 缓存的值</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> timeout 时间</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> timeUnit 时间颗粒度</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> &lt;T&gt; <span class="keyword">void</span> <span class="title function_">setCacheObject</span><span class="params">(<span class="keyword">final</span> String key, <span class="keyword">final</span> T value, <span class="keyword">final</span> Integer timeout, <span class="keyword">final</span> TimeUnit timeUnit)</span></span><br><span class="line">    &#123;</span><br><span class="line">        redisTemplate.opsForValue().set(key, value, timeout, timeUnit);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 设置有效时间</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> key Redis键</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> timeout 超时时间</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> true=设置成功；false=设置失败</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">expire</span><span class="params">(<span class="keyword">final</span> String key, <span class="keyword">final</span> <span class="type">long</span> timeout)</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">return</span> expire(key, timeout, TimeUnit.SECONDS);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 设置有效时间</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> key Redis键</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> timeout 超时时间</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> unit 时间单位</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> true=设置成功；false=设置失败</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">expire</span><span class="params">(<span class="keyword">final</span> String key, <span class="keyword">final</span> <span class="type">long</span> timeout, <span class="keyword">final</span> TimeUnit unit)</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">return</span> redisTemplate.expire(key, timeout, unit);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 获得缓存的基本对象。</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> key 缓存键值</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 缓存键值对应的数据</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> &lt;T&gt; T <span class="title function_">getCacheObject</span><span class="params">(<span class="keyword">final</span> String key)</span></span><br><span class="line">    &#123;</span><br><span class="line">        ValueOperations&lt;String, T&gt; operation = redisTemplate.opsForValue();</span><br><span class="line">        <span class="keyword">return</span> operation.get(key);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 删除单个对象</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> key</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">deleteObject</span><span class="params">(<span class="keyword">final</span> String key)</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">return</span> redisTemplate.delete(key);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 删除集合对象</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> collection 多个对象</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">long</span> <span class="title function_">deleteObject</span><span class="params">(<span class="keyword">final</span> Collection collection)</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">return</span> redisTemplate.delete(collection);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 缓存List数据</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> key 缓存的键值</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> dataList 待缓存的List数据</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 缓存的对象</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> &lt;T&gt; <span class="type">long</span> <span class="title function_">setCacheList</span><span class="params">(<span class="keyword">final</span> String key, <span class="keyword">final</span> List&lt;T&gt; dataList)</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="type">Long</span> <span class="variable">count</span> <span class="operator">=</span> redisTemplate.opsForList().rightPushAll(key, dataList);</span><br><span class="line">        <span class="keyword">return</span> count == <span class="literal">null</span> ? <span class="number">0</span> : count;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 获得缓存的list对象</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> key 缓存的键值</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 缓存键值对应的数据</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> &lt;T&gt; List&lt;T&gt; <span class="title function_">getCacheList</span><span class="params">(<span class="keyword">final</span> String key)</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">return</span> redisTemplate.opsForList().range(key, <span class="number">0</span>, -<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 缓存Set</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> key 缓存键值</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> dataSet 缓存的数据</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 缓存数据的对象</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> &lt;T&gt; BoundSetOperations&lt;String, T&gt; <span class="title function_">setCacheSet</span><span class="params">(<span class="keyword">final</span> String key, <span class="keyword">final</span> Set&lt;T&gt; dataSet)</span></span><br><span class="line">    &#123;</span><br><span class="line">        BoundSetOperations&lt;String, T&gt; setOperation = redisTemplate.boundSetOps(key);</span><br><span class="line">        Iterator&lt;T&gt; it = dataSet.iterator();</span><br><span class="line">        <span class="keyword">while</span> (it.hasNext())</span><br><span class="line">        &#123;</span><br><span class="line">            setOperation.add(it.next());</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> setOperation;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 获得缓存的set</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> key</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> &lt;T&gt; Set&lt;T&gt; <span class="title function_">getCacheSet</span><span class="params">(<span class="keyword">final</span> String key)</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">return</span> redisTemplate.opsForSet().members(key);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 缓存Map</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> key</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> dataMap</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> &lt;T&gt; <span class="keyword">void</span> <span class="title function_">setCacheMap</span><span class="params">(<span class="keyword">final</span> String key, <span class="keyword">final</span> Map&lt;String, T&gt; dataMap)</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span> (dataMap != <span class="literal">null</span>) &#123;</span><br><span class="line">            redisTemplate.opsForHash().putAll(key, dataMap);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 获得缓存的Map</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> key</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> &lt;T&gt; Map&lt;String, T&gt; <span class="title function_">getCacheMap</span><span class="params">(<span class="keyword">final</span> String key)</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">return</span> redisTemplate.opsForHash().entries(key);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 往Hash中存入数据</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> key Redis键</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> hKey Hash键</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> value 值</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> &lt;T&gt; <span class="keyword">void</span> <span class="title function_">setCacheMapValue</span><span class="params">(<span class="keyword">final</span> String key, <span class="keyword">final</span> String hKey, <span class="keyword">final</span> T value)</span></span><br><span class="line">    &#123;</span><br><span class="line">        redisTemplate.opsForHash().put(key, hKey, value);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 获取Hash中的数据</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> key Redis键</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> hKey Hash键</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> Hash中的对象</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> &lt;T&gt; T <span class="title function_">getCacheMapValue</span><span class="params">(<span class="keyword">final</span> String key, <span class="keyword">final</span> String hKey)</span></span><br><span class="line">    &#123;</span><br><span class="line">        HashOperations&lt;String, String, T&gt; opsForHash = redisTemplate.opsForHash();</span><br><span class="line">        <span class="keyword">return</span> opsForHash.get(key, hKey);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 删除Hash中的数据</span></span><br><span class="line"><span class="comment">     * </span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> key</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> hkey</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">delCacheMapValue</span><span class="params">(<span class="keyword">final</span> String key, <span class="keyword">final</span> String hkey)</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="type">HashOperations</span> <span class="variable">hashOperations</span> <span class="operator">=</span> redisTemplate.opsForHash();</span><br><span class="line">        hashOperations.delete(key, hkey);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 获取多个Hash中的数据</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> key Redis键</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> hKeys Hash键集合</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> Hash对象集合</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> &lt;T&gt; List&lt;T&gt; <span class="title function_">getMultiCacheMapValue</span><span class="params">(<span class="keyword">final</span> String key, <span class="keyword">final</span> Collection&lt;Object&gt; hKeys)</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">return</span> redisTemplate.opsForHash().multiGet(key, hKeys);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 获得缓存的基本对象列表</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> pattern 字符串前缀</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 对象列表</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> Collection&lt;String&gt; <span class="title function_">keys</span><span class="params">(<span class="keyword">final</span> String pattern)</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">return</span> redisTemplate.keys(pattern);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">package</span> com.ydlclass.authdemo.utils;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> javax.servlet.http.HttpServletResponse;</span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">WebUtils</span> &#123;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 将字符串渲染到客户端</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> response 渲染对象</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> string   待渲染的字符串</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> null</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> String <span class="title function_">renderString</span><span class="params">(HttpServletResponse response, String string)</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            response.setStatus(<span class="number">200</span>);</span><br><span class="line">            response.setContentType(<span class="string">&quot;application/json&quot;</span>);</span><br><span class="line">            response.setCharacterEncoding(<span class="string">&quot;utf-8&quot;</span>);</span><br><span class="line">            response.getWriter().print(string);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>⑤实体类</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.io.Serializable;</span><br><span class="line"><span class="keyword">import</span> java.util.Date;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="meta">@AllArgsConstructor</span></span><br><span class="line"><span class="meta">@NoArgsConstructor</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">User</span> <span class="keyword">implements</span> <span class="title class_">Serializable</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">long</span> <span class="variable">serialVersionUID</span> <span class="operator">=</span> <span class="number">1L</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">    * 主键</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">    <span class="keyword">private</span> Long id;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">    * 用户名</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">    <span class="keyword">private</span> String userName;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">    * 昵称</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">    <span class="keyword">private</span> String nickName;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">    * 密码</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">    <span class="keyword">private</span> String password;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">    * 账号状态（0正常 1停用）</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">    <span class="keyword">private</span> String status;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">    * 邮箱</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">    <span class="keyword">private</span> String email;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">    * 手机号</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">    <span class="keyword">private</span> String phonenumber;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">    * 用户性别（0男，1女，2未知）</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">    <span class="keyword">private</span> String sex;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">    * 头像</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">    <span class="keyword">private</span> String avatar;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">    * 用户类型（0管理员，1普通用户）</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">    <span class="keyword">private</span> String userType;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">    * 创建人的用户id</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">    <span class="keyword">private</span> Long createBy;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">    * 创建时间</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">    <span class="keyword">private</span> Date createTime;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">    * 更新人</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">    <span class="keyword">private</span> Long updateBy;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">    * 更新时间</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">    <span class="keyword">private</span> Date updateTime;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">    * 删除标志（0代表未删除，1代表已删除）</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">    <span class="keyword">private</span> Integer delFlag;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h4 id="4-实现"><a href="#4-实现" class="headerlink" title="#(4) 实现"></a><a target="_blank" rel="noopener" href="https://ydlclass.com/doc21xnv/java/third/framework/8%E3%80%81SpringSecurity/#_4-%E5%AE%9E%E7%8E%B0">#</a>(4) 实现</h4><h5 id="a-数据库校验用户"><a href="#a-数据库校验用户" class="headerlink" title="#a. 数据库校验用户"></a><a target="_blank" rel="noopener" href="https://ydlclass.com/doc21xnv/java/third/framework/8%E3%80%81SpringSecurity/#a-%E6%95%B0%E6%8D%AE%E5%BA%93%E6%A0%A1%E9%AA%8C%E7%94%A8%E6%88%B7">#</a>a. 数据库校验用户</h5><p>自定义一个UserDetailsService的实现类,让SpringSecurity使用我们的实现类。从数据库中查询用户名和密码。</p>
<p>准备工作</p>
<p>ydl_security库中。我们先创建一个系统的用户表， 建表语句如下：</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">DROP</span> <span class="keyword">TABLE</span> IF <span class="keyword">EXISTS</span> `sys_user`;</span><br><span class="line"><span class="keyword">CREATE</span> <span class="keyword">TABLE</span> `sys_user` (</span><br><span class="line">  `id` <span class="type">bigint</span> <span class="keyword">NOT</span> <span class="keyword">NULL</span> AUTO_INCREMENT COMMENT <span class="string">&#x27;主键&#x27;</span>,</span><br><span class="line">  `user_name` <span class="type">varchar</span>(<span class="number">64</span>) <span class="keyword">NOT</span> <span class="keyword">NULL</span> <span class="keyword">DEFAULT</span> <span class="string">&#x27;NULL&#x27;</span> COMMENT <span class="string">&#x27;用户名&#x27;</span>,</span><br><span class="line">  `nick_name` <span class="type">varchar</span>(<span class="number">64</span>) <span class="keyword">NOT</span> <span class="keyword">NULL</span> <span class="keyword">DEFAULT</span> <span class="string">&#x27;NULL&#x27;</span> COMMENT <span class="string">&#x27;昵称&#x27;</span>,</span><br><span class="line">  `password` <span class="type">varchar</span>(<span class="number">64</span>) <span class="keyword">NOT</span> <span class="keyword">NULL</span> <span class="keyword">DEFAULT</span> <span class="string">&#x27;NULL&#x27;</span> COMMENT <span class="string">&#x27;密码&#x27;</span>,</span><br><span class="line">  `status` <span class="type">char</span>(<span class="number">1</span>) <span class="keyword">DEFAULT</span> <span class="string">&#x27;0&#x27;</span> COMMENT <span class="string">&#x27;账号状态（0正常 1停用）&#x27;</span>,</span><br><span class="line">  `email` <span class="type">varchar</span>(<span class="number">64</span>) <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;邮箱&#x27;</span>,</span><br><span class="line">  `phonenumber` <span class="type">varchar</span>(<span class="number">32</span>) <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;手机号&#x27;</span>,</span><br><span class="line">  `sex` <span class="type">char</span>(<span class="number">1</span>) <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;用户性别（0男，1女，2未知）&#x27;</span>,</span><br><span class="line">  `avatar` <span class="type">varchar</span>(<span class="number">128</span>) <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;头像&#x27;</span>,</span><br><span class="line">  `user_type` <span class="type">char</span>(<span class="number">1</span>) <span class="keyword">NOT</span> <span class="keyword">NULL</span> <span class="keyword">DEFAULT</span> <span class="string">&#x27;1&#x27;</span> COMMENT <span class="string">&#x27;用户类型（0管理员，1普通用户）&#x27;</span>,</span><br><span class="line">  `create_by` <span class="type">bigint</span> <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;创建人的用户id&#x27;</span>,</span><br><span class="line">  `create_time` datetime <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;创建时间&#x27;</span>,</span><br><span class="line">  `update_by` <span class="type">bigint</span> <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;更新人&#x27;</span>,</span><br><span class="line">  `update_time` datetime <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;更新时间&#x27;</span>,</span><br><span class="line">  `del_flag` <span class="type">int</span> <span class="keyword">DEFAULT</span> <span class="string">&#x27;0&#x27;</span> COMMENT <span class="string">&#x27;删除标志（0代表未删除，1代表已删除）&#x27;</span>,</span><br><span class="line">  <span class="keyword">PRIMARY</span> KEY (`id`)</span><br><span class="line">) ENGINE<span class="operator">=</span>InnoDB AUTO_INCREMENT<span class="operator">=</span><span class="number">2</span> <span class="keyword">DEFAULT</span> CHARSET<span class="operator">=</span>utf8mb4 <span class="keyword">COLLATE</span><span class="operator">=</span>utf8mb4_0900_ai_ci COMMENT<span class="operator">=</span><span class="string">&#x27;用户表&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- ----------------------------</span></span><br><span class="line"><span class="comment">-- Records of sys_user</span></span><br><span class="line"><span class="comment">-- ----------------------------</span></span><br><span class="line"><span class="keyword">INSERT</span> <span class="keyword">INTO</span> `sys_user` <span class="keyword">VALUES</span> (<span class="string">&#x27;1&#x27;</span>, <span class="string">&#x27;itlils&#x27;</span>, <span class="string">&#x27;IT李老师&#x27;</span>, <span class="string">&#x27;$2a$10$UNfN3sUdNmka5cxCmrWHf.EZs6yRTztTvwoLJWXGf6VjRz/ABJ9y2&#x27;</span>, <span class="string">&#x27;0&#x27;</span>, <span class="string">&#x27;123@qq.com&#x27;</span>, <span class="string">&#x27;13012345678&#x27;</span>, <span class="string">&#x27;0&#x27;</span>, <span class="string">&#x27;a&#x27;</span>, <span class="string">&#x27;1&#x27;</span>, <span class="string">&#x27;1&#x27;</span>, <span class="string">&#x27;2022-08-20 18:52:41&#x27;</span>, <span class="string">&#x27;1&#x27;</span>, <span class="string">&#x27;2022-08-21 18:52:49&#x27;</span>, <span class="string">&#x27;0&#x27;</span>);</span><br></pre></td></tr></table></figure>

<p>引入MybatisPuls和mysql驱动的依赖</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.baomidou<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>mybatis-plus-boot-starter<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>3.4.3<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>mysql<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>mysql-connector-java<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p>配置数据库信息</p>
<figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">datasource:</span></span><br><span class="line">    <span class="attr">url:</span> <span class="string">jdbc:mysql://localhost:3306/ydl_security?characterEncoding=utf-8&amp;serverTimezone=UTC</span></span><br><span class="line">    <span class="attr">username:</span> <span class="string">root</span></span><br><span class="line">    <span class="attr">password:</span> <span class="string">ydlclass666</span></span><br><span class="line">    <span class="attr">driver-class-name:</span> <span class="string">com.mysql.cj.jdbc.Driver</span></span><br></pre></td></tr></table></figure>

<p>定义Mapper接口</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">UserMapper</span> <span class="keyword">extends</span> <span class="title class_">BaseMapper</span>&lt;User&gt; &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>修改User实体类</p>
<figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">类名上加@TableName(value = &quot;sys_user&quot;) ,id字段上加 @TableId</span><br></pre></td></tr></table></figure>

<p>启动类配置Mapper扫描</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@MapperScan(&quot;com.ydlclass.authdemo.dao&quot;)</span></span><br></pre></td></tr></table></figure>

<p>测试</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootTest</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">AuthDemoApplicationTests</span> &#123;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    UserMapper userMapper;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">contextLoads</span><span class="params">()</span> &#123;</span><br><span class="line">        List&lt;User&gt; users = userMapper.selectList(<span class="literal">null</span>);</span><br><span class="line">        System.out.println(users.get(<span class="number">0</span>));</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>结果</p>
<p><img src="https://ydlclass.com/doc21xnv/assets/image-20220821223919369-e9454d90.png" class="lazyload" data-srcset="https://ydlclass.com/doc21xnv/assets/image-20220821223919369-e9454d90.png" srcset="" alt="image-20220821223919369">image-20220821223919369</p>
<p><strong>重要！</strong>实现真实从数据库获取系统用户信息</p>
<p>创建UserDetailsService实现类，重写其中的方法。用户名从数据库中查询用户信息。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserDetailsServiceImpl</span> <span class="keyword">implements</span> <span class="title class_">UserDetailsService</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> UserMapper userMapper;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> UserDetails <span class="title function_">loadUserByUsername</span><span class="params">(String username)</span> <span class="keyword">throws</span> UsernameNotFoundException &#123;</span><br><span class="line">        <span class="comment">//根据用户名查询用户信息</span></span><br><span class="line">        LambdaQueryWrapper&lt;User&gt; wrapper = <span class="keyword">new</span> <span class="title class_">LambdaQueryWrapper</span>&lt;&gt;();</span><br><span class="line">        wrapper.eq(User::getUserName,username);</span><br><span class="line">        <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> userMapper.selectOne(wrapper);</span><br><span class="line">        <span class="comment">//如果查询不到数据就通过抛出异常来给出提示</span></span><br><span class="line">        <span class="keyword">if</span>(Objects.isNull(user))&#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(<span class="string">&quot;用户名错误&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">//TODO 根据用户查询权限信息 添加到LoginUser中</span></span><br><span class="line">        </span><br><span class="line">        </span><br><span class="line">        <span class="comment">//封装成UserDetails对象返回 </span></span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">LoginUser</span>(user);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>因为UserDetailsService方法的返回值是UserDetails类型，所以需要定义一个类，实现该接口，把用户信息封装在其中。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="meta">@NoArgsConstructor</span></span><br><span class="line"><span class="meta">@AllArgsConstructor</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LoginUser</span> <span class="keyword">implements</span> <span class="title class_">UserDetails</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> User user;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Collection&lt;? <span class="keyword">extends</span> <span class="title class_">GrantedAuthority</span>&gt; getAuthorities() &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getPassword</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> user.getPassword();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getUsername</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> user.getUserName();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isAccountNonExpired</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isAccountNonLocked</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isCredentialsNonExpired</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isEnabled</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>注意：如果要测试，需要往用户表中写入用户数据，并且如果你想让用户的密码是明文存储，需要在密码前加{noop}。例如</p>
<p><img src="" alt="image-20220821224611161">image-20220821224611161</p>
<p>这样登陆的时候就可以用sg作为用户名，ydlclass作为密码来登陆了。</p>
<h5 id="b-密码加密存储"><a href="#b-密码加密存储" class="headerlink" title="#b.密码加密存储"></a><a target="_blank" rel="noopener" href="https://ydlclass.com/doc21xnv/java/third/framework/8%E3%80%81SpringSecurity/#b-%E5%AF%86%E7%A0%81%E5%8A%A0%E5%AF%86%E5%AD%98%E5%82%A8">#</a>b.密码加密存储</h5><p> 1 实际项目中我们不会把密码明文存储在数据库中</p>
<p> 2 默认使用的PasswordEncoder要求数据库中的密码格式为：{id}password 。它会根据id去判断密码的加密方式。但是我们一般不会采用这种方式。所以就需要替换PasswordEncoder。</p>
<p> 我们一般使用SpringSecurity为我们提供的BCryptPasswordEncoder。</p>
<p> 我们只需要使用把BCryptPasswordEncoder对象注入Spring容器中，SpringSecurity就会使用该PasswordEncoder来进行密码校验。</p>
<p> 我们可以定义一个SpringSecurity的配置类，SpringSecurity要求这个配置类要继承WebSecurityConfigurerAdapter。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SecurityConfig</span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> PasswordEncoder <span class="title function_">passwordEncoder</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">BCryptPasswordEncoder</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>密码加密：</p>
<p>1 12345 md5 –&gt; asdfasdfasdfasdfa 默认密码12345</p>
<p>2 12345 md5(12345|itlils)—-&gt;uiowertupouert 加盐</p>
<p>3 BCryptPasswordEncoder 自动加盐</p>
<p>过时问题：</p>
<p>首先，过时也能用，如果看着不爽，可以使用如下方法。</p>
<p>以前我们自定义类继承自 WebSecurityConfigurerAdapter 来配置我们的 Spring Security，我们主要是配置两个东西：</p>
<ul>
<li>configure(HttpSecurity)</li>
<li>configure(WebSecurity)</li>
</ul>
<p>前者主要是配置 Spring Security 中的过滤器链，后者则主要是配置一些路径放行规则。</p>
<p>现在在 WebSecurityConfigurerAdapter 的注释中，人家已经把意思说的很明白了：</p>
<ul>
<li>以后如果想要配置过滤器链，可以通过自定义 SecurityFilterChainBean来实现。</li>
<li>以后如果想要配置 WebSecurity，可以通过 WebSecurityCustomizerBean来实现。</li>
</ul>
<p>如</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> WebSecurityCustomizer <span class="title function_">webSecurityCustomizer</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> (web) -&gt; web.ignoring().antMatchers(<span class="string">&quot;/images/**&quot;</span>, <span class="string">&quot;/js/**&quot;</span>, <span class="string">&quot;/webjars/**&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Bean</span></span><br><span class="line">   <span class="keyword">public</span>  SecurityFilterChain <span class="title function_">securityFilterChain</span><span class="params">()</span> &#123;</span><br><span class="line">       List&amp;lt;Filter&amp;gt; filters = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&amp;lt;&amp;gt;();</span><br><span class="line">       <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">DefaultSecurityFilterChain</span>(<span class="keyword">new</span> <span class="title class_">AntPathRequestMatcher</span>(<span class="string">&quot;/**&quot;</span>), filters);</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//如果想好看</span></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> SecurityFilterChain <span class="title function_">filterChain</span><span class="params">(HttpSecurity http)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        http.authorizeRequests().antMatchers(<span class="string">&quot;/login&quot;</span>).permitAll()</span><br><span class="line">                .antMatchers(<span class="string">&quot;/users/**&quot;</span>, <span class="string">&quot;/settings/**&quot;</span>).hasAuthority(<span class="string">&quot;Admin&quot;</span>)</span><br><span class="line">                .hasAnyAuthority(<span class="string">&quot;Admin&quot;</span>, <span class="string">&quot;Editor&quot;</span>, <span class="string">&quot;Salesperson&quot;</span>)</span><br><span class="line">                .hasAnyAuthority(<span class="string">&quot;Admin&quot;</span>, <span class="string">&quot;Editor&quot;</span>, <span class="string">&quot;Salesperson&quot;</span>, <span class="string">&quot;Shipper&quot;</span>)</span><br><span class="line">                .anyRequest().authenticated()</span><br><span class="line">                .and().formLogin()</span><br><span class="line">                .loginPage(<span class="string">&quot;/login&quot;</span>)</span><br><span class="line">                .usernameParameter(<span class="string">&quot;email&quot;</span>)</span><br><span class="line">                .permitAll()</span><br><span class="line">                .and()</span><br><span class="line">                .rememberMe().key(<span class="string">&quot;AbcdEfghIjklmNopQrsTuvXyz_0123456789&quot;</span>)</span><br><span class="line">                .and()</span><br><span class="line">                .logout().permitAll();</span><br><span class="line"> </span><br><span class="line">        http.headers().frameOptions().sameOrigin();</span><br><span class="line"> </span><br><span class="line">        <span class="keyword">return</span> http.build();</span><br><span class="line">    &#125;&#125;   </span><br></pre></td></tr></table></figure>

<h5 id="c-自定义登陆接口"><a href="#c-自定义登陆接口" class="headerlink" title="#c.自定义登陆接口"></a><a target="_blank" rel="noopener" href="https://ydlclass.com/doc21xnv/java/third/framework/8%E3%80%81SpringSecurity/#c-%E8%87%AA%E5%AE%9A%E4%B9%89%E7%99%BB%E9%99%86%E6%8E%A5%E5%8F%A3">#</a>c.自定义登陆接口</h5><p>分析需求：</p>
<p>1 自定义一个controller登陆接口</p>
<p>2 放行自定义登陆接口</p>
<p>3使用ProviderManager auth方法进行验证</p>
<p>4自己生成jwt给前端</p>
<p>5系统用户相关所有信息放入redis</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.ydlclass.authdemo.controller;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> com.ydlclass.authdemo.domain.ResponseResult;</span><br><span class="line"><span class="keyword">import</span> com.ydlclass.authdemo.domain.User;</span><br><span class="line"><span class="keyword">import</span> com.ydlclass.authdemo.service.LoginService;</span><br><span class="line"><span class="keyword">import</span> org.springframework.beans.factory.annotation.Autowired;</span><br><span class="line"><span class="keyword">import</span> org.springframework.web.bind.annotation.PostMapping;</span><br><span class="line"><span class="keyword">import</span> org.springframework.web.bind.annotation.RequestBody;</span><br><span class="line"><span class="keyword">import</span> org.springframework.web.bind.annotation.RestController;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Created</span> by IT李老师</span></span><br><span class="line"><span class="comment"> * 公主号 “元动力课堂”</span></span><br><span class="line"><span class="comment"> * 个人微 itlils</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LoginController</span> &#123;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    LoginService loginService;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@PostMapping(&quot;/user/login&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> ResponseResult <span class="title function_">login</span><span class="params">(<span class="meta">@RequestBody</span> User user)</span>&#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> loginService.login(user);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">package</span> com.ydlclass.authdemo.config;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.springframework.beans.factory.annotation.Autowired;</span><br><span class="line"><span class="keyword">import</span> org.springframework.context.annotation.Bean;</span><br><span class="line"><span class="keyword">import</span> org.springframework.context.annotation.Configuration;</span><br><span class="line"><span class="keyword">import</span> org.springframework.security.authentication.AuthenticationManager;</span><br><span class="line"><span class="keyword">import</span> org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;</span><br><span class="line"><span class="keyword">import</span> org.springframework.security.config.annotation.web.builders.HttpSecurity;</span><br><span class="line"><span class="keyword">import</span> org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;</span><br><span class="line"><span class="keyword">import</span> org.springframework.security.config.http.SessionCreationPolicy;</span><br><span class="line"><span class="keyword">import</span> org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;</span><br><span class="line"><span class="keyword">import</span> org.springframework.security.crypto.password.PasswordEncoder;</span><br><span class="line"><span class="keyword">import</span> org.springframework.security.web.SecurityFilterChain;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SecurityConfig</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> PasswordEncoder <span class="title function_">passwordEncoder</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">BCryptPasswordEncoder</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> SecurityFilterChain <span class="title function_">filterChain</span><span class="params">(HttpSecurity http)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">                http</span><br><span class="line">                <span class="comment">//关闭csrf</span></span><br><span class="line">                .csrf().disable()</span><br><span class="line">                <span class="comment">//不通过Session获取SecurityContext</span></span><br><span class="line">                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)</span><br><span class="line">                .and()</span><br><span class="line">                .authorizeRequests()</span><br><span class="line">                <span class="comment">// 对于登录接口 允许匿名访问</span></span><br><span class="line">                .antMatchers(<span class="string">&quot;/user/login&quot;</span>).anonymous()</span><br><span class="line">                <span class="comment">// 除上面外的所有请求全部需要鉴权认证</span></span><br><span class="line">                .anyRequest().authenticated();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> http.build();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> AuthenticationConfiguration authenticationConfiguration;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> AuthenticationManager <span class="title function_">authenticationManager</span><span class="params">()</span> <span class="keyword">throws</span> Exception&#123;</span><br><span class="line">        <span class="type">AuthenticationManager</span> <span class="variable">authenticationManager</span> <span class="operator">=</span> authenticationConfiguration.getAuthenticationManager();</span><br><span class="line">        <span class="keyword">return</span> authenticationManager;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">package</span> com.ydlclass.authdemo.service.impl;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> com.ydlclass.authdemo.domain.LoginUser;</span><br><span class="line"><span class="keyword">import</span> com.ydlclass.authdemo.domain.ResponseResult;</span><br><span class="line"><span class="keyword">import</span> com.ydlclass.authdemo.domain.User;</span><br><span class="line"><span class="keyword">import</span> com.ydlclass.authdemo.service.LoginService;</span><br><span class="line"><span class="keyword">import</span> com.ydlclass.authdemo.utils.JwtUtil;</span><br><span class="line"><span class="keyword">import</span> com.ydlclass.authdemo.utils.RedisCache;</span><br><span class="line"><span class="keyword">import</span> org.springframework.beans.factory.annotation.Autowired;</span><br><span class="line"><span class="keyword">import</span> org.springframework.security.authentication.AuthenticationManager;</span><br><span class="line"><span class="keyword">import</span> org.springframework.security.authentication.ProviderManager;</span><br><span class="line"><span class="keyword">import</span> org.springframework.security.authentication.UsernamePasswordAuthenticationToken;</span><br><span class="line"><span class="keyword">import</span> org.springframework.security.core.Authentication;</span><br><span class="line"><span class="keyword">import</span> org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;</span><br><span class="line"><span class="keyword">import</span> org.springframework.stereotype.Service;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.util.HashMap;</span><br><span class="line"><span class="keyword">import</span> java.util.Map;</span><br><span class="line"><span class="keyword">import</span> java.util.Objects;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Created</span> by IT李老师</span></span><br><span class="line"><span class="comment"> * 公主号 “元动力课堂”</span></span><br><span class="line"><span class="comment"> * 个人微 itlils</span></span><br><span class="line"><span class="comment"> * 规矩：缺啥补啥  干就完事儿 照猫画虎</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LoginServiceImpl</span> <span class="keyword">implements</span> <span class="title class_">LoginService</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    AuthenticationManager authenticationManager;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    RedisCache redisCache;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> ResponseResult <span class="title function_">login</span><span class="params">(User user)</span> &#123;</span><br><span class="line">        <span class="comment">//3使用ProviderManager auth方法进行验证</span></span><br><span class="line">        <span class="type">UsernamePasswordAuthenticationToken</span> <span class="variable">usernamePasswordAuthenticationToken</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">UsernamePasswordAuthenticationToken</span>(user.getUserName(),user.getPassword());</span><br><span class="line">        <span class="type">Authentication</span> <span class="variable">authenticate</span> <span class="operator">=</span> authenticationManager.authenticate(usernamePasswordAuthenticationToken);</span><br><span class="line"></span><br><span class="line">        <span class="comment">//校验失败了</span></span><br><span class="line">        <span class="keyword">if</span>(Objects.isNull(authenticate))&#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(<span class="string">&quot;用户名或密码错误！&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">//4自己生成jwt给前端</span></span><br><span class="line">        LoginUser loginUser= (LoginUser)(authenticate.getPrincipal());</span><br><span class="line">        <span class="type">String</span> <span class="variable">userId</span> <span class="operator">=</span> loginUser.getUser().getId().toString();</span><br><span class="line">        <span class="type">String</span> <span class="variable">jwt</span> <span class="operator">=</span> JwtUtil.createJWT(userId);</span><br><span class="line">        Map&lt;String,String&gt; map=<span class="keyword">new</span> <span class="title class_">HashMap</span>();</span><br><span class="line">        map.put(<span class="string">&quot;token&quot;</span>,jwt);</span><br><span class="line">        <span class="comment">//5系统用户相关所有信息放入redis</span></span><br><span class="line">        redisCache.setCacheObject(<span class="string">&quot;login:&quot;</span>+userId,loginUser);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ResponseResult</span>(<span class="number">200</span>,<span class="string">&quot;登陆成功&quot;</span>,map);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h5 id="d-认证过滤器"><a href="#d-认证过滤器" class="headerlink" title="#d.认证过滤器"></a><a target="_blank" rel="noopener" href="https://ydlclass.com/doc21xnv/java/third/framework/8%E3%80%81SpringSecurity/#d-%E8%AE%A4%E8%AF%81%E8%BF%87%E6%BB%A4%E5%99%A8">#</a>d.认证过滤器</h5><p>1.获取token</p>
<p>2.解析token</p>
<p>3.获取userid</p>
<p>4.封装Authentication</p>
<p>5.存入SecurityContextHolder</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.ydlclass.authdemo.filter;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> com.ydlclass.authdemo.domain.LoginUser;</span><br><span class="line"><span class="keyword">import</span> com.ydlclass.authdemo.utils.JwtUtil;</span><br><span class="line"><span class="keyword">import</span> com.ydlclass.authdemo.utils.RedisCache;</span><br><span class="line"><span class="keyword">import</span> io.jsonwebtoken.Claims;</span><br><span class="line"><span class="keyword">import</span> org.springframework.beans.factory.annotation.Autowired;</span><br><span class="line"><span class="keyword">import</span> org.springframework.security.authentication.UsernamePasswordAuthenticationToken;</span><br><span class="line"><span class="keyword">import</span> org.springframework.security.core.context.SecurityContextHolder;</span><br><span class="line"><span class="keyword">import</span> org.springframework.stereotype.Component;</span><br><span class="line"><span class="keyword">import</span> org.springframework.util.StringUtils;</span><br><span class="line"><span class="keyword">import</span> org.springframework.web.filter.OncePerRequestFilter;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> javax.servlet.FilterChain;</span><br><span class="line"><span class="keyword">import</span> javax.servlet.ServletException;</span><br><span class="line"><span class="keyword">import</span> javax.servlet.http.HttpServletRequest;</span><br><span class="line"><span class="keyword">import</span> javax.servlet.http.HttpServletResponse;</span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"><span class="keyword">import</span> java.util.Objects;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Created</span> by IT李老师</span></span><br><span class="line"><span class="comment"> * 公主号 “元动力课堂”</span></span><br><span class="line"><span class="comment"> * 个人微 itlils</span></span><br><span class="line"><span class="comment"> * OncePerRequestFilter 只走一次，请求前</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">JwtAuthenticationTokenFilter</span> <span class="keyword">extends</span> <span class="title class_">OncePerRequestFilter</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    RedisCache redisCache;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">doFilterInternal</span><span class="params">(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)</span> <span class="keyword">throws</span> ServletException, IOException &#123;</span><br><span class="line"></span><br><span class="line">        <span class="comment">//1获取token  header的token</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">token</span> <span class="operator">=</span> request.getHeader(<span class="string">&quot;token&quot;</span>);</span><br><span class="line">        <span class="keyword">if</span> (!StringUtils.hasText(token)) &#123;</span><br><span class="line">            <span class="comment">//放行，让后面的过滤器执行</span></span><br><span class="line">            filterChain.doFilter(request, response);</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">//2解析token</span></span><br><span class="line">        String userId;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="type">Claims</span> <span class="variable">claims</span> <span class="operator">=</span> JwtUtil.parseJWT(token);</span><br><span class="line">            userId = claims.getSubject();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(<span class="string">&quot;token不合法！&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">//3获取userId, redis获取用户信息</span></span><br><span class="line">        <span class="type">LoginUser</span> <span class="variable">loginUser</span> <span class="operator">=</span> redisCache.getCacheObject(<span class="string">&quot;login:&quot;</span> + userId);</span><br><span class="line">        <span class="keyword">if</span> (Objects.isNull(loginUser)) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(<span class="string">&quot;当前用户未登录！&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">//4封装Authentication</span></span><br><span class="line">        <span class="type">UsernamePasswordAuthenticationToken</span> <span class="variable">usernamePasswordAuthenticationToken</span></span><br><span class="line">                <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">UsernamePasswordAuthenticationToken</span>(loginUser, <span class="literal">null</span>, <span class="literal">null</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">//5存入SecurityContextHolder</span></span><br><span class="line">        SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);</span><br><span class="line"></span><br><span class="line">        <span class="comment">//放行，让后面的过滤器执行</span></span><br><span class="line">        filterChain.doFilter(request, response);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">package</span> com.ydlclass.authdemo.config;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.springframework.beans.factory.annotation.Autowired;</span><br><span class="line"><span class="keyword">import</span> org.springframework.context.annotation.Bean;</span><br><span class="line"><span class="keyword">import</span> org.springframework.context.annotation.Configuration;</span><br><span class="line"><span class="keyword">import</span> org.springframework.security.authentication.AuthenticationManager;</span><br><span class="line"><span class="keyword">import</span> org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;</span><br><span class="line"><span class="keyword">import</span> org.springframework.security.config.annotation.web.builders.HttpSecurity;</span><br><span class="line"><span class="keyword">import</span> org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;</span><br><span class="line"><span class="keyword">import</span> org.springframework.security.config.http.SessionCreationPolicy;</span><br><span class="line"><span class="keyword">import</span> org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;</span><br><span class="line"><span class="keyword">import</span> org.springframework.security.crypto.password.PasswordEncoder;</span><br><span class="line"><span class="keyword">import</span> org.springframework.security.web.SecurityFilterChain;</span><br><span class="line"><span class="keyword">import</span> org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SecurityConfig</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> PasswordEncoder <span class="title function_">passwordEncoder</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">BCryptPasswordEncoder</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;</span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> SecurityFilterChain <span class="title function_">filterChain</span><span class="params">(HttpSecurity http)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">                http</span><br><span class="line">                <span class="comment">//关闭csrf</span></span><br><span class="line">                .csrf().disable()</span><br><span class="line">                <span class="comment">//不通过Session获取SecurityContext</span></span><br><span class="line">                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)</span><br><span class="line">                .and()</span><br><span class="line">                .authorizeRequests()</span><br><span class="line">                <span class="comment">// 对于登录接口 允许匿名访问</span></span><br><span class="line">                .antMatchers(<span class="string">&quot;/user/login&quot;</span>).anonymous()</span><br><span class="line">                <span class="comment">// 除上面外的所有请求全部需要鉴权认证</span></span><br><span class="line">                .anyRequest().authenticated();</span><br><span class="line"></span><br><span class="line">        <span class="comment">//把token校验过滤器添加到过滤器链中</span></span><br><span class="line">        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> http.build();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> AuthenticationConfiguration authenticationConfiguration;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> AuthenticationManager <span class="title function_">authenticationManager</span><span class="params">()</span> <span class="keyword">throws</span> Exception&#123;</span><br><span class="line">        <span class="type">AuthenticationManager</span> <span class="variable">authenticationManager</span> <span class="operator">=</span> authenticationConfiguration.getAuthenticationManager();</span><br><span class="line">        <span class="keyword">return</span> authenticationManager;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>测试：</p>
<p>登陆</p>
<p>未登陆访问资源的话</p>
<p><img src="https://ydlclass.com/doc21xnv/assets/image-20220822024122118-96f2759b.png" class="lazyload" data-srcset="https://ydlclass.com/doc21xnv/assets/image-20220822024122118-96f2759b.png" srcset="" alt="image-20220822024122118">image-20220822024122118</p>
<p>携带token访问</p>
<p><img src="https://ydlclass.com/doc21xnv/assets/image-20220822024036753-88642562.png" class="lazyload" data-srcset="https://ydlclass.com/doc21xnv/assets/image-20220822024036753-88642562.png" srcset="" alt="image-20220822024036753">image-20220822024036753</p>
<h5 id="e-退出登陆"><a href="#e-退出登陆" class="headerlink" title="#e.退出登陆"></a><a target="_blank" rel="noopener" href="https://ydlclass.com/doc21xnv/java/third/framework/8%E3%80%81SpringSecurity/#e-%E9%80%80%E5%87%BA%E7%99%BB%E9%99%86">#</a>e.退出登陆</h5><p>写一个退出接口，删除reids里的key</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.ydlclass.authdemo.controller;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> com.ydlclass.authdemo.domain.ResponseResult;</span><br><span class="line"><span class="keyword">import</span> com.ydlclass.authdemo.domain.User;</span><br><span class="line"><span class="keyword">import</span> com.ydlclass.authdemo.service.LoginService;</span><br><span class="line"><span class="keyword">import</span> org.springframework.beans.factory.annotation.Autowired;</span><br><span class="line"><span class="keyword">import</span> org.springframework.web.bind.annotation.PostMapping;</span><br><span class="line"><span class="keyword">import</span> org.springframework.web.bind.annotation.RequestBody;</span><br><span class="line"><span class="keyword">import</span> org.springframework.web.bind.annotation.RestController;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Created</span> by IT李老师</span></span><br><span class="line"><span class="comment"> * 公主号 “元动力课堂”</span></span><br><span class="line"><span class="comment"> * 个人微 itlils</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LoginController</span> &#123;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    LoginService loginService;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@PostMapping(&quot;/user/login&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> ResponseResult <span class="title function_">login</span><span class="params">(<span class="meta">@RequestBody</span> User user)</span>&#123;</span><br><span class="line">        <span class="keyword">return</span> loginService.login(user);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@PostMapping(&quot;/user/logout&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> ResponseResult <span class="title function_">logout</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="keyword">return</span> loginService.logout();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">package</span> com.ydlclass.authdemo.service.impl;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> com.ydlclass.authdemo.domain.LoginUser;</span><br><span class="line"><span class="keyword">import</span> com.ydlclass.authdemo.domain.ResponseResult;</span><br><span class="line"><span class="keyword">import</span> com.ydlclass.authdemo.domain.User;</span><br><span class="line"><span class="keyword">import</span> com.ydlclass.authdemo.service.LoginService;</span><br><span class="line"><span class="keyword">import</span> com.ydlclass.authdemo.utils.JwtUtil;</span><br><span class="line"><span class="keyword">import</span> com.ydlclass.authdemo.utils.RedisCache;</span><br><span class="line"><span class="keyword">import</span> org.springframework.beans.factory.annotation.Autowired;</span><br><span class="line"><span class="keyword">import</span> org.springframework.security.authentication.AuthenticationManager;</span><br><span class="line"><span class="keyword">import</span> org.springframework.security.authentication.ProviderManager;</span><br><span class="line"><span class="keyword">import</span> org.springframework.security.authentication.UsernamePasswordAuthenticationToken;</span><br><span class="line"><span class="keyword">import</span> org.springframework.security.core.Authentication;</span><br><span class="line"><span class="keyword">import</span> org.springframework.security.core.context.SecurityContextHolder;</span><br><span class="line"><span class="keyword">import</span> org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;</span><br><span class="line"><span class="keyword">import</span> org.springframework.stereotype.Service;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.util.HashMap;</span><br><span class="line"><span class="keyword">import</span> java.util.Map;</span><br><span class="line"><span class="keyword">import</span> java.util.Objects;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Created</span> by IT李老师</span></span><br><span class="line"><span class="comment"> * 公主号 “元动力课堂”</span></span><br><span class="line"><span class="comment"> * 个人微 itlils</span></span><br><span class="line"><span class="comment"> * 规矩：缺啥补啥  干就完事儿 照猫画虎</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LoginServiceImpl</span> <span class="keyword">implements</span> <span class="title class_">LoginService</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    AuthenticationManager authenticationManager;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    RedisCache redisCache;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> ResponseResult <span class="title function_">login</span><span class="params">(User user)</span> &#123;</span><br><span class="line">        <span class="comment">//3使用ProviderManager auth方法进行验证</span></span><br><span class="line">        <span class="type">UsernamePasswordAuthenticationToken</span> <span class="variable">usernamePasswordAuthenticationToken</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">UsernamePasswordAuthenticationToken</span>(user.getUserName(),user.getPassword());</span><br><span class="line">        <span class="type">Authentication</span> <span class="variable">authenticate</span> <span class="operator">=</span> authenticationManager.authenticate(usernamePasswordAuthenticationToken);</span><br><span class="line"></span><br><span class="line">        <span class="comment">//校验失败了</span></span><br><span class="line">        <span class="keyword">if</span>(Objects.isNull(authenticate))&#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(<span class="string">&quot;用户名或密码错误！&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">//4自己生成jwt给前端</span></span><br><span class="line">        LoginUser loginUser= (LoginUser)(authenticate.getPrincipal());</span><br><span class="line">        <span class="type">String</span> <span class="variable">userId</span> <span class="operator">=</span> loginUser.getUser().getId().toString();</span><br><span class="line">        <span class="type">String</span> <span class="variable">jwt</span> <span class="operator">=</span> JwtUtil.createJWT(userId);</span><br><span class="line">        Map&lt;String,String&gt; map=<span class="keyword">new</span> <span class="title class_">HashMap</span>();</span><br><span class="line">        map.put(<span class="string">&quot;token&quot;</span>,jwt);</span><br><span class="line">        <span class="comment">//5系统用户相关所有信息放入redis</span></span><br><span class="line">        redisCache.setCacheObject(<span class="string">&quot;login:&quot;</span>+userId,loginUser);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ResponseResult</span>(<span class="number">200</span>,<span class="string">&quot;登陆成功&quot;</span>,map);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> ResponseResult <span class="title function_">logout</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">Authentication</span> <span class="variable">authentication</span> <span class="operator">=</span> SecurityContextHolder.getContext().getAuthentication();</span><br><span class="line">        <span class="type">LoginUser</span> <span class="variable">loginUser</span> <span class="operator">=</span> (LoginUser) authentication.getPrincipal();</span><br><span class="line">        <span class="type">Long</span> <span class="variable">userId</span> <span class="operator">=</span> loginUser.getUser().getId();</span><br><span class="line">        redisCache.deleteObject(<span class="string">&quot;login:&quot;</span>+userId);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ResponseResult</span>(<span class="number">200</span>,<span class="string">&quot;退出成功！&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>测试</p>
<p>登陆：</p>
<p><img src="https://ydlclass.com/doc21xnv/assets/image-20220822031306918-04a9d9fd.png" class="lazyload" data-srcset="https://ydlclass.com/doc21xnv/assets/image-20220822031306918-04a9d9fd.png" srcset="" alt="image-20220822031306918">image-20220822031306918</p>
<p>访问资源：</p>
<p><img src="https://ydlclass.com/doc21xnv/assets/image-20220822031335809-190ea849.png" class="lazyload" data-srcset="https://ydlclass.com/doc21xnv/assets/image-20220822031335809-190ea849.png" srcset="" alt="image-20220822031335809">image-20220822031335809</p>
<p>退出：</p>
<p><img src="https://ydlclass.com/doc21xnv/assets/image-20220822031151323-a6ea73b1.png" class="lazyload" data-srcset="https://ydlclass.com/doc21xnv/assets/image-20220822031151323-a6ea73b1.png" srcset="" alt="image-20220822031151323">image-20220822031151323</p>
<p>访问资源</p>
<p><img src="https://ydlclass.com/doc21xnv/assets/image-20220822031222868-32cf0665.png" class="lazyload" data-srcset="https://ydlclass.com/doc21xnv/assets/image-20220822031222868-32cf0665.png" srcset="" alt="image-20220822031222868">image-20220822031222868</p>
<p>security配置文件 连式编程</p>
<p>未登录、已登录都能访问permitAll()</p>
<p><img src="https://ydlclass.com/doc21xnv/assets/image-20220822032300261-051cb27e.png" class="lazyload" data-srcset="https://ydlclass.com/doc21xnv/assets/image-20220822032300261-051cb27e.png" srcset="" alt="image-20220822032300261">image-20220822032300261</p>
<p>只能未登录访问 anonymous()</p>
<p><img src="https://ydlclass.com/doc21xnv/assets/image-20220822032407738-e65b3beb.png" class="lazyload" data-srcset="https://ydlclass.com/doc21xnv/assets/image-20220822032407738-e65b3beb.png" srcset="" alt="image-20220822032407738"></p>
<h1 id="SpringSecurity快速入门"><a href="#SpringSecurity快速入门" class="headerlink" title="SpringSecurity快速入门"></a>SpringSecurity快速入门</h1><h2 id="自定义登录"><a href="#自定义登录" class="headerlink" title="自定义登录"></a>自定义登录</h2><p>通过设置配置文件来实现自定义登录</p>
<figure class="highlight properties"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">spring.security.user.name</span>=<span class="string">yitian</span></span><br><span class="line"><span class="attr">spring.security.user.password</span>=<span class="string">yitian</span></span><br></pre></td></tr></table></figure>






  </div>
  
  
    
    <div class='footer'>
       <!-- 参考资料、相关资料等 -->
      
       <!-- 相关文章 -->
      
      <!-- 版权声明组件 -->
      
      <!-- 打赏组件 -->
      
    </div>
  
  
    


  <div class='article-meta' id="bottom">
    <div class='new-meta-box'>
      
        
          <div class="new-meta-item date" itemprop="dateModified" datetime="2024-01-24T13:24:15+08:00">
  <a class='notlink'>
    <i class="fa-solid fa-edit fa-fw" aria-hidden="true"></i>
    <p>更新于：Jan 24, 2024</p>
  </a>
</div>

        
      
        
          

        
      
        
          
  <div class="new-meta-item share -mob-share-list">
  <div class="-mob-share-list share-body">
    
      
        <a class="-mob-share-qq" title="" rel="external nofollow noopener noreferrer noopener"
          
          target="_blank" href="http://connect.qq.com/widget/shareqq/index.html?url=http://8.219.11.28/2024/02/23/%E5%89%8D%E5%90%8E%E7%AB%AF-SpringSecurity/&title= - 欢迎来到我的个人博客~&summary="
          
          >
          
            <img src="https://unpkg.com/volantis-static@0.0.1654736714924/media/org.volantis/logo/128/qq.png" class="lazyload" data-srcset="https://unpkg.com/volantis-static@0.0.1654736714924/media/org.volantis/logo/128/qq.png" srcset="">
          
        </a>
      
    
      
        <a class="-mob-share-qzone" title="" rel="external nofollow noopener noreferrer noopener"
          
          target="_blank" href="https://sns.qzone.qq.com/cgi-bin/qzshare/cgi_qzshare_onekey?url=http://8.219.11.28/2024/02/23/%E5%89%8D%E5%90%8E%E7%AB%AF-SpringSecurity/&title= - 欢迎来到我的个人博客~&summary="
          
          >
          
            <img src="https://unpkg.com/volantis-static@0.0.1654736714924/media/org.volantis/logo/128/qzone.png" class="lazyload" data-srcset="https://unpkg.com/volantis-static@0.0.1654736714924/media/org.volantis/logo/128/qzone.png" srcset="">
          
        </a>
      
    
      
        <a class="-mob-share-weibo" title="" rel="external nofollow noopener noreferrer noopener"
          
          target="_blank" href="http://service.weibo.com/share/share.php?url=http://8.219.11.28/2024/02/23/%E5%89%8D%E5%90%8E%E7%AB%AF-SpringSecurity/&title= - 欢迎来到我的个人博客~&summary="
          
          >
          
            <img src="https://unpkg.com/volantis-static@0.0.1654736714924/media/org.volantis/logo/128/weibo.png" class="lazyload" data-srcset="https://unpkg.com/volantis-static@0.0.1654736714924/media/org.volantis/logo/128/weibo.png" srcset="">
          
        </a>
      
    
      
    
      
    
  </div>
</div>



        
      
    </div>
    <!-- Custom Files bottomMeta begin -->
    
    <!-- Custom Files bottomMeta end -->
  </div>


  
  

  
    <div class="prev-next">
      
        <a class='prev' href='/2024/02/23/%E5%89%8D%E5%90%8E%E7%AB%AF-SpringMVC/'>
          <p class='title'><i class="fa-solid fa-chevron-left" aria-hidden="true"></i>Feb 23, 2024</p>
          <p class='content'>SpringMVCSpringMVC各组件的生动比喻当我们谈论Spring MVC时，可以将其比喻为一家餐厅的运作方式。让我们将Spring MVC的各个组件比喻为餐厅中的不同元素：

Disp...</p>
        </a>
      
      
        <a class='next' href='/2024/02/23/%E5%89%8D%E5%90%8E%E7%AB%AF-SpringMVC%E8%AF%BE%E5%A0%82%E7%AC%94%E8%AE%B0/'>
          <p class='title'>Feb 23, 2024<i class="fa-solid fa-chevron-right" aria-hidden="true"></i></p>
          <p class='content'>SpringMVC课堂笔记1、SpringMVC概述1.1 SpringMVC概念SpringMVC 也叫 Spring web mvc。是 Spring内置的一个MVC框架，在 Spring3...</p>
        </a>
      
    </div>
  
  <!-- Custom Files postEnd begin-->
  
  <!-- Custom Files postEnd end-->
</article>


  


  <article class="post white-box shadow floatable blur" id="comments">
    <span hidden>
      <meta itemprop="discussionUrl" content="/2024/02/23/%E5%89%8D%E5%90%8E%E7%AB%AF-SpringSecurity/index.html#comments">
    </span>
    <p ct><i class='fa-solid fa-comments'></i> 评论</p>
    

    <div id="layoutHelper-comments"></div>

  </article>






</div>
<aside id='l_side' itemscope itemtype="http://schema.org/WPSideBar">
  

  
    
    
      
    
  


<div class="widget-sticky pjax">

  
  


  <section class="widget toc-wrapper desktop mobile " id="toc-div" >
    
  <header>
    
      <i class="fa-solid fa-list fa-fw" aria-hidden="true"></i><span class='name'>本文目录</span>
    
  </header>


    <div class='content'>
        <ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%A6%82%E5%BF%B5"><span class="toc-text">概念</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E8%AE%A4%E8%AF%81-%E9%89%B4%E6%9D%83"><span class="toc-text">认证 鉴权</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%85%A5%E9%97%A8"><span class="toc-text">入门</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%87%86%E5%A4%87web%E9%A1%B9%E7%9B%AE"><span class="toc-text">准备web项目</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%EF%BC%881%EF%BC%89%E5%88%9B%E5%BB%BAspringboot-web%E9%A1%B9%E7%9B%AE"><span class="toc-text">（1）创建springboot web项目</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%BC%95%E5%85%A5SpringSecurity"><span class="toc-text">引入SpringSecurity</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%EF%BC%881%EF%BC%89%E5%BC%95%E5%85%A5%E4%BE%9D%E8%B5%96"><span class="toc-text">（1）引入依赖</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%EF%BC%882%EF%BC%89%E6%B5%8B%E8%AF%95"><span class="toc-text">（2）测试</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%EF%BC%883%EF%BC%89%E8%87%AA%E5%B8%A6%E9%80%80%E5%87%BA"><span class="toc-text">（3）自带退出</span></a></li></ol></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E8%AE%A4%E8%AF%81"><span class="toc-text">认证</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#1%E3%80%81web%E7%99%BB%E9%99%86%E6%B5%81%E7%A8%8B"><span class="toc-text">1、web登陆流程</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E7%BC%BA%E7%82%B9%E5%8F%AF%E4%BB%A5%E6%94%B9%E8%BF%9B%EF%BC%9A"><span class="toc-text">缺点可以改进：</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2%E3%80%81%E7%9C%8B%E6%BA%90%E7%A0%81"><span class="toc-text">2、看源码</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%EF%BC%881%EF%BC%89springsecurity-%E7%99%BB%E9%99%86%E6%B5%81%E7%A8%8B"><span class="toc-text">（1）springsecurity 登陆流程</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%EF%BC%882%EF%BC%89%E8%AE%A4%E8%AF%81%E6%B5%81%E7%A8%8B-%E5%86%8D%E7%BB%86%E5%8C%96%EF%BC%81-%E4%BA%86%E8%A7%A3%EF%BC%8C%E5%90%8E%E6%9C%9F%E5%9B%9E%E5%A4%B4%E7%9C%8B"><span class="toc-text">（2）认证流程 再细化！ 了解，后期回头看</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#3%E3%80%81%E8%87%AA%E5%AE%9A%E4%B9%89%E7%99%BB%E5%BD%95"><span class="toc-text">3、自定义登录</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%EF%BC%881%EF%BC%89%E6%80%9D%E8%B7%AF"><span class="toc-text">（1）思路</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%EF%BC%882%EF%BC%89-JWT%E7%AE%80%E4%BB%8B"><span class="toc-text">（2） JWT简介</span></a><ol class="toc-child"><li class="toc-item toc-level-5"><a class="toc-link" href="#a-%E6%A6%82%E5%BF%B5"><span class="toc-text">a.概念</span></a></li><li class="toc-item toc-level-5"><a class="toc-link" href="#b-JJWT%E7%AD%BE%E5%8F%91%E4%B8%8E%E9%AA%8C%E8%AF%81token"><span class="toc-text">b. JJWT签发与验证token</span></a></li><li class="toc-item toc-level-5"><a class="toc-link" href="#c-%E5%88%9B%E5%BB%BAtoken"><span class="toc-text">c. 创建token</span></a></li><li class="toc-item toc-level-5"><a class="toc-link" href="#d-%E8%A7%A3%E6%9E%90token"><span class="toc-text">d.解析token</span></a></li><li class="toc-item toc-level-5"><a class="toc-link" href="#e-%E8%AE%BE%E7%BD%AE%E8%BF%87%E6%9C%9F%E6%97%B6%E9%97%B4"><span class="toc-text">e. 设置过期时间</span></a></li><li class="toc-item toc-level-5"><a class="toc-link" href="#f-%E8%87%AA%E5%AE%9A%E4%B9%89claims"><span class="toc-text">f.自定义claims</span></a></li></ol></li><li class="toc-item toc-level-4"><a class="toc-link" href="#3-%E5%87%86%E5%A4%87%E6%96%B0%E9%A1%B9%E7%9B%AE"><span class="toc-text">(3) 准备新项目</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#4-%E5%AE%9E%E7%8E%B0"><span class="toc-text">(4) 实现</span></a><ol class="toc-child"><li class="toc-item toc-level-5"><a class="toc-link" href="#a-%E6%95%B0%E6%8D%AE%E5%BA%93%E6%A0%A1%E9%AA%8C%E7%94%A8%E6%88%B7"><span class="toc-text">a. 数据库校验用户</span></a></li><li class="toc-item toc-level-5"><a class="toc-link" href="#b-%E5%AF%86%E7%A0%81%E5%8A%A0%E5%AF%86%E5%AD%98%E5%82%A8"><span class="toc-text">b.密码加密存储</span></a></li><li class="toc-item toc-level-5"><a class="toc-link" href="#c-%E8%87%AA%E5%AE%9A%E4%B9%89%E7%99%BB%E9%99%86%E6%8E%A5%E5%8F%A3"><span class="toc-text">c.自定义登陆接口</span></a></li><li class="toc-item toc-level-5"><a class="toc-link" href="#d-%E8%AE%A4%E8%AF%81%E8%BF%87%E6%BB%A4%E5%99%A8"><span class="toc-text">d.认证过滤器</span></a></li><li class="toc-item toc-level-5"><a class="toc-link" href="#e-%E9%80%80%E5%87%BA%E7%99%BB%E9%99%86"><span class="toc-text">e.退出登陆</span></a></li></ol></li></ol></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E8%87%AA%E5%AE%9A%E4%B9%89%E7%99%BB%E5%BD%95"><span class="toc-text">自定义登录</span></a></li></ol>
    </div>
  </section>

  

</div>


<!-- 没有 pjax 占位会报错 万恶的 pjax -->

  <div class="pjax">
    <!-- pjax占位 -->
  </div>

  <div class="pjax">
    <!-- pjax占位 -->
  </div>

  <div class="pjax">
    <!-- pjax占位 -->
  </div>

  <div class="pjax">
    <!-- pjax占位 -->
  </div>

  <div class="pjax">
    <!-- pjax占位 -->
  </div>

  <div class="pjax">
    <!-- pjax占位 -->
  </div>

  <div class="pjax">
    <!-- pjax占位 -->
  </div>

  <div class="pjax">
    <!-- pjax占位 -->
  </div>

  <!-- Custom Files side begin -->
  
  <!-- Custom Files side end -->
</aside>



          <!--此文件用来存放一些不方便取值的变量-->
<!--思路大概是将值藏到重加载的区域内-->

<pjax>
<script>
  window.pdata={}
  pdata.ispage=false;
  pdata.commentPath="";
  pdata.commentPlaceholder="";
  pdata.commentConfig={};
  //  see: /layout/_partial/scripts/_ctrl/coverCtrl.ejs
  
    // header
    var l_header=document.getElementById("l_header");
    
    l_header.classList.add("show");
    
    
      // cover
      var cover_wrapper=document.querySelector('#l_cover .cover-wrapper');
      var scroll_down=document.getElementById('scroll-down');
      cover_wrapper.id="none";
      cover_wrapper.style.display="none";
      scroll_down.style.display="none";
    
  
</script>
</pjax>
        </div>
        
  
  <footer class="footer clearfix"  itemscope itemtype="http://schema.org/WPFooter">
    <br><br>
    
      
        <div class="aplayer-container">
          


        </div>
      
    
      
        <br>
        <div class="social-wrapper" itemprop="about" itemscope itemtype="http://schema.org/Thing">
          
            
          
            
          
            
          
        </div>
      
    
      
        <div><p>Blog content follows the <a target="_blank" rel="noopener" href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en">Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0) License</a></p>
</div>
      
    
      
        
          <div><p><span id="lc-sv">本站总访问量为 <span id='number'><i class="fa-solid fa-loader fa-spin fa-fw" aria-hidden="true"></i></span> 次</span> <span id="lc-uv">访客数为 <span id='number'><i class="fa-solid fa-loader fa-spin fa-fw" aria-hidden="true"></i></span> 人</span></p>
</div>
        
      
    
      
        Use
        <a href="https://github.com/volantis-x/hexo-theme-volantis/#5.8.0" target="_blank" class="codename">Volantis</a>
        as theme
      
    
      
        <div class='copyright'>
        <p><a href="/">Copyright © since 2017 XXX</a></p>

        </div>
      
    
    <!-- Custom Files footer begin-->
    
    <!-- Custom Files footer end-->
  </footer>


        <a id="s-top" class="fa-solid fa-arrow-up fa-fw" href="/" onclick="return false;" title="top"></a>
      </div>
    </div>
    <div>
      <script>
  /******************** volantis.dom ********************************/
  // 页面选择器 将dom对象缓存起来 see: /source/js/app.js etc.
  volantis.dom.bodyAnchor = volantis.dom.$(document.getElementById("safearea")); // 页面主体
  volantis.dom.topBtn = volantis.dom.$(document.getElementById('s-top')); // 向上
  volantis.dom.wrapper = volantis.dom.$(document.getElementById('wrapper')); // 整个导航栏
  volantis.dom.coverAnchor = volantis.dom.$(document.querySelector('#l_cover .cover-wrapper')); // 1个
  volantis.dom.switcher = volantis.dom.$(document.querySelector('#l_header .switcher .s-search')); // 搜索按钮   移动端 1个
  volantis.dom.header = volantis.dom.$(document.getElementById('l_header')); // 移动端导航栏
  volantis.dom.search = volantis.dom.$(document.querySelector('#l_header .m_search')); // 搜索框 桌面端 移动端 1个
  volantis.dom.mPhoneList = volantis.dom.$(document.querySelectorAll('#l_header .m-phone .list-v')); //  手机端 子菜单 多个
</script>

<script>
  
  volantis.css("https://unpkg.com/volantis-static@0.0.1654736714924/libs/@fortawesome/fontawesome-free/css/all.min.css");
  
  
  
</script>

<!-- required -->


<!-- internal -->

<script src="/js/app.js"></script>






<!-- rightmenu要在darkmode之前（ToggleButton） darkmode要在comments之前（volantis.dark.push）-->



<script>
  function loadIssuesJS() {
    
      const sites_api = document.getElementById('sites-api');
      if (sites_api != undefined && typeof SitesJS === 'undefined') {
        volantis.js("/js/plugins/tags/sites.js")
      }
    
    
      const friends_api = document.getElementById('friends-api');
      if (friends_api != undefined && typeof FriendsJS === 'undefined') {
        volantis.js("/js/plugins/tags/friends.js")
      }
    
    
      const contributors_api = document.getElementById('contributors-api');
      if (contributors_api != undefined && typeof ContributorsJS === 'undefined') {
        volantis.js("/js/plugins/tags/contributors.js")
      }
    
  };
  loadIssuesJS()
  volantis.pjax.push(()=>{
    loadIssuesJS();
  })

</script>




  <script defer src="https://unpkg.com/volantis-static@0.0.1654736714924/libs/vanilla-lazyload/dist/lazyload.min.js"></script>
<script>
  // https://www.npmjs.com/package/vanilla-lazyload
  // Set the options globally
  // to make LazyLoad self-initialize
  window.lazyLoadOptions = {
    elements_selector: ".lazyload",
    threshold: 0
  };
  // Listen to the initialization event
  // and get the instance of LazyLoad
  window.addEventListener(
    "LazyLoad::Initialized",
    function (event) {
      window.lazyLoadInstance = event.detail.instance;
    },
    false
  );
  document.addEventListener('DOMContentLoaded', function () {
    lazyLoadInstance.update();
  });
  document.addEventListener('pjax:complete', function () {
    lazyLoadInstance.update();
  });
</script>




  

<script>
  window.FPConfig = {
	delay: 0,
	ignoreKeywords: ["#"],
	maxRPS: 6,
	hoverDelay: 0
  };
</script>
<script defer src="https://unpkg.com/volantis-static@0.0.1654736714924/libs/flying-pages/flying-pages.min.js"></script>









      <script>
  volantis.layoutHelper("comments",`<div id="giscus_container"></div>`)

  volantis.giscus = {};

  function check_giscus() {
    if (volantis.dark.mode === "dark") {
      volantis.giscus.Theme = 'dark';
    } else {
      volantis.giscus.Theme = 'light';
    }

    return document.getElementById("giscus_container");
  }

  function pjax_giscus() {
    const HEAD = check_giscus();
    if (!HEAD) return;
    let cfg = Object.assign({"theme":{"light":"light","dark":"dark"}},pdata.commentConfig)
    const script = document.createElement('script');
    script.setAttribute('src', 'https://giscus.app/client.js');
    Object.keys(cfg).forEach(k=>{
      if (k != "theme") {
        script.setAttribute('data-'+k, cfg[k]);
      }
    })
    script.setAttribute('data-theme', volantis.giscus.Theme);
    script.setAttribute('crossorigin', "anonymous");
    HEAD.appendChild(script);
  }

  function dark_giscus() {
    const HEAD = check_giscus();
    if (!HEAD) return;

    const message = {
      setConfig: {
        theme: volantis.giscus.Theme
      }
    };
    const giscusIframe = document.querySelector('iframe.giscus-frame');
    giscusIframe.contentWindow.postMessage({ giscus: message }, 'https://giscus.app');
  }
  pjax_giscus();
  volantis.pjax.push(pjax_giscus);
  volantis.dark.push(dark_giscus);
</script>

    





<!-- optional -->

  <script>
  const SearchServiceDataPathRoot = ("/" || "/").endsWith("/") ?
    "/" || "/" :
    "//" || "/";
  const SearchServiceDataPath = SearchServiceDataPathRoot + "content.json";

  function loadSearchScript() {
    // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs
    return volantis.js("/js/search/hexo.js");
  }

  function loadSearchService() {
    loadSearchScript();
    document.querySelectorAll(".input.u-search-input").forEach((e) => {
      e.removeEventListener("focus", loadSearchService, false);
    });

    document.querySelectorAll(".u-search-form").forEach((e) => {
      e.addEventListener("submit", (event) => {
        event.preventDefault();
      }, false);
    });
  }

  // 打开并搜索 字符串 s
  function OpenSearch(s) {
    if (typeof SearchService === 'undefined')
      loadSearchScript().then(() => {
        SearchService.setQueryText(s);
        SearchService.search();
      });
    else {
      SearchService.setQueryText(s);
      SearchService.search();
    }
  }

  // 访问含有 ?s=xxx  的链接时打开搜索 // 与搜索引擎 structured data 相关: /scripts/helpers/structured-data/lib/config.js
  if (window.location.search && /^\?s=/g.test(window.location.search)) {
    let queryText = decodeURI(window.location.search)
      .replace(/\ /g, "-")
      .replace(/^\?s=/g, "");
    OpenSearch(queryText);
  }

  // 搜索输入框获取焦点时加载搜索
  document.querySelectorAll(".input.u-search-input").forEach((e) => {
    e.addEventListener("focus", loadSearchService, false);
  });
</script>







  <script>



  function pjax_highlightjs_copyCode(){
    if (!(document.querySelector(".highlight .code pre") ||
      document.querySelector(".article pre code"))) {
      return;
    }
    VolantisApp.utilCopyCode(".highlight .code pre, .article pre code")
  }
  volantis.requestAnimationFrame(pjax_highlightjs_copyCode)
  volantis.pjax.push(pjax_highlightjs_copyCode)

</script>












  <script>
  function load_swiper() {
    if (!document.querySelectorAll(".swiper-container")[0]) return;
    volantis.css("https://unpkg.com/volantis-static@0.0.1654736714924/libs/swiper/swiper-bundle.min.css");
    volantis.js("https://unpkg.com/volantis-static@0.0.1654736714924/libs/swiper/swiper-bundle.min.js").then(() => {
      pjax_swiper();
    });
  }

  load_swiper();

  function pjax_swiper() {
    volantis.swiper = new Swiper('.swiper-container', {
      slidesPerView: 'auto',
      spaceBetween: 8,
      centeredSlides: true,
      loop: true,
      pagination: {
        el: '.swiper-pagination',
        clickable: true,
      },
      navigation: {
        nextEl: '.swiper-button-next',
        prevEl: '.swiper-button-prev',
      },
    });
  }

  volantis.pjax.push(() => {
    if (!document.querySelectorAll(".swiper-container")[0]) return;
    if (typeof volantis.swiper === "undefined") {
      load_swiper();
    } else {
      pjax_swiper();
    }
  });
</script>


<!-- pjax 标签必须存在于所有页面 否则 pjax error -->
<pjax>

</pjax>

<script>
  function listennSidebarTOC() {
    const navItems = document.querySelectorAll(".toc li");
    if (!navItems.length) return;
    let targets = []
    const sections = [...navItems].map((element) => {
      const link = element.querySelector(".toc-link");
      const target = document.getElementById(
        decodeURI(link.getAttribute("href")).replace("#", "")
      );
      targets.push(target)
      // 解除 a 标签 href 的 锚点定位, a 标签 href 的 锚点定位 会随机启用?? 产生错位???
      link.setAttribute("onclick","return false;")
      link.setAttribute("toc-action","toc-"+decodeURI(link.getAttribute("href")).replace("#", ""))
      link.setAttribute("href","/")
      // 配置 点击 触发新的锚点定位
      link.addEventListener("click", (event) => {
        event.preventDefault();
        // 这里的 addTop 是通过错位使得 toc 自动展开.
        volantis.scroll.to(target,{addTop: 5, observer:true})
        // Anchor id
        history.pushState(null, document.title, "#" + target.id);
      });
      return target;
    });

    function activateNavByIndex(target) {
      if (target.classList.contains("active-current")) return;

      document.querySelectorAll(".toc .active").forEach((element) => {
        element.classList.remove("active", "active-current");
      });
      target.classList.add("active", "active-current");
      let parent = target.parentNode;
      while (!parent.matches(".toc")) {
        if (parent.matches("li")) parent.classList.add("active");
        parent = parent.parentNode;
      }
    }

    // 方案一：
    volantis.activateNavIndex=0
    activateNavByIndex(navItems[volantis.activateNavIndex])
    volantis.scroll.push(()=>{
      if (targets[0].getBoundingClientRect().top >= 0) {
        volantis.activateNavIndex = 0
      }else if (targets[targets.length-1].getBoundingClientRect().top < 0) {
        volantis.activateNavIndex = targets.length-1
      } else {
        for (let index = 0; index < targets.length; index++) {
          const target0 = targets[index];
          const target1 = targets[(index+1)%targets.length];
          if (target0.getBoundingClientRect().top < 0&&target1.getBoundingClientRect().top >= 0) {
            volantis.activateNavIndex=index
            break;
          }
        }
      }
      activateNavByIndex(navItems[volantis.activateNavIndex])
    })

    // 方案二：
    // IntersectionObserver 不是完美精确到像素级别 也不是低延时性的
    // function findIndex(entries) {
    //   let index = 0;
    //   let entry = entries[index];
    //   if (entry.boundingClientRect.top > 0) {
    //     index = sections.indexOf(entry.target);
    //     return index === 0 ? 0 : index - 1;
    //   }
    //   for (; index < entries.length; index++) {
    //     if (entries[index].boundingClientRect.top <= 0) {
    //       entry = entries[index];
    //     } else {
    //       return sections.indexOf(entry.target);
    //     }
    //   }
    //   return sections.indexOf(entry.target);
    // }
    // function createIntersectionObserver(marginTop) {
    //   marginTop = Math.floor(marginTop + 10000);
    //   let intersectionObserver = new IntersectionObserver(
    //     (entries, observe) => {
    //       let scrollHeight = document.documentElement.scrollHeight;
    //       if (scrollHeight > marginTop) {
    //         observe.disconnect();
    //         createIntersectionObserver(scrollHeight);
    //         return;
    //       }
    //       let index = findIndex(entries);
    //       activateNavByIndex(navItems[index]);
    //     }, {
    //       rootMargin: marginTop + "px 0px -100% 0px",
    //       threshold: 0,
    //     }
    //   );
    //   sections.forEach((element) => {
    //     element && intersectionObserver.observe(element);
    //   });
    // }
    // createIntersectionObserver(document.documentElement.scrollHeight);
  }

  document.addEventListener("DOMContentLoaded", ()=>{
    volantis.requestAnimationFrame(listennSidebarTOC)
  });
  document.addEventListener("pjax:success", ()=>{
    volantis.requestAnimationFrame(listennSidebarTOC)
  });
</script>



<script>
  document.onreadystatechange = function () {
    if (document.readyState == 'complete') {
      // 页面加载完毕 样式加载失败，或是当前网速慢，或是开启了省流模式
      const { saveData, effectiveType } = navigator.connection || navigator.mozConnection || navigator.webkitConnection || {}
      if (getComputedStyle(document.querySelector("#safearea"), null)["display"] == "none" || saveData || /2g/.test(effectiveType)) {
        document.querySelectorAll(".reveal").forEach(function (e) {
          e.style["opacity"] = "1";
        });
        document.querySelector("#safearea").style["display"] = "block";
      }
    }
  }
</script>


  <script type="application/ld+json">[{"@context":"http://schema.org","@type":"Organization","name":"欢迎来到我的个人博客~","url":"http://8.219.11.28/","logo":{"@type":"ImageObject","url":"https://unpkg.com/volantis-static@0.0.1654736714924/media/org.volantis/blog/favicon/android-chrome-192x192.png","width":192,"height":192}},{"@context":"http://schema.org","@type":"Person","name":"天明","image":{"@type":"ImageObject","url":"https://unpkg.com/volantis-static@0.0.1654736714924/media/org.volantis/blog/favicon/android-chrome-192x192.png"},"url":"http://8.219.11.28/","sameAs":["https://github.com/volantis-x"],"description":"这些技术大部分是自己总结的，非官方"},{"@context":"http://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"http://8.219.11.28/","name":"欢迎来到我的个人博客~"}},{"@type":"ListItem","position":3,"item":{"@id":"http://8.219.11.28/2024/02/23/前后端-SpringSecurity/"}}]},{"@context":"http://schema.org","@type":"WebSite","name":"欢迎来到我的个人博客~","url":"http://8.219.11.28/","keywords":null,"description":"这些技术大部分是自己总结的，非官方","author":{"@type":"Person","name":"天明","image":{"@type":"ImageObject","url":"https://unpkg.com/volantis-static@0.0.1654736714924/media/org.volantis/blog/favicon/android-chrome-192x192.png"},"url":"http://8.219.11.28/","description":"这些技术大部分是自己总结的，非官方"},"publisher":{"@type":"Organization","name":"欢迎来到我的个人博客~","url":"http://8.219.11.28/","logo":{"@type":"ImageObject","url":"https://unpkg.com/volantis-static@0.0.1654736714924/media/org.volantis/blog/favicon/android-chrome-192x192.png","width":192,"height":192}},"potentialAction":{"@type":"SearchAction","name":"Site Search","target":{"@type":"EntryPoint","urlTemplate":"http://8.219.11.28?s={search_term_string}"},"query-input":"required name=search_term_string"}},{"@context":"http://schema.org","@type":"BlogPosting","description":"这些技术大部分是自己总结的，非官方","inLanguage":"en","mainEntityOfPage":{"@type":"WebPage","@id":"http://8.219.11.28/2024/02/23/%E5%89%8D%E5%90%8E%E7%AB%AF-SpringSecurity/"},"author":{"@type":"Person","name":"天明","image":{"@type":"ImageObject","url":"https://unpkg.com/volantis-static@0.0.1654736714924/media/org.volantis/blog/favicon/android-chrome-192x192.png"},"url":"http://8.219.11.28/"},"publisher":{"@type":"Organization","name":"欢迎来到我的个人博客~","logo":{"@type":"ImageObject","url":"https://unpkg.com/volantis-static@0.0.1654736714924/media/org.volantis/blog/favicon/android-chrome-192x192.png","width":192,"height":192}},"url":"http://8.219.11.28/2024/02/23/%E5%89%8D%E5%90%8E%E7%AB%AF-SpringSecurity/","wordCount":0,"datePublished":"2024-02-23T14:27:45.499Z","dateModified":"2024-01-24T05:24:15.042Z","image":{"@type":"ImageObject","url":"https://unpkg.com/volantis-static@0.0.1654736714924/media/org.volantis/blog/favicon/android-chrome-192x192.png","width":192,"height":192}}]</script>



      
        <!--
  pjax重载区域接口：
  1.  <pjax></pjax> 标签 pjax 标签必须存在于所有页面 否则 pjax error
  2.  script[data-pjax]
  3.  .pjax-reload script
  4.  .pjax
-->



<script src="https://unpkg.com/volantis-static@0.0.1654736714924/libs/pjax/pjax.min.js"></script>


<script>
    var pjax;
    document.addEventListener('DOMContentLoaded', function () {
      pjax = new Pjax({
        elements: 'a[href]:not([href^="#"]):not([href="javascript:void(0)"]):not([pjax-fancybox]):not([onclick="return false;"]):not([onclick="return!1"]):not([target="_blank"]):not([target="view_window"]):not([href$=".xml"])',
        selectors: [
          "head title",
          "head meta[name=keywords]",
          "head meta[name=description]",
          
          "#l_main",
          "#pjax-header-nav-list",
          ".pjax",
          "pjax", // <pjax></pjax> 标签
          "script[data-pjax], .pjax-reload script" // script标签添加data-pjax 或 script标签外层添加.pjax-reload 的script代码段重载
        ],
        cacheBust: false,   // url 地址追加时间戳，用以避免浏览器缓存
        timeout: 5000,
        
      });
    });

    document.addEventListener('pjax:send', function (e) {
      //window.stop(); // 相当于点击了浏览器的停止按钮

      try {
        var currentUrl = window.location.pathname;
        var targetUrl = e.triggerElement.href;
        var banUrl = [""];
        if (banUrl[0] != "") {
          banUrl.forEach(item => {
            if(currentUrl.indexOf(item) != -1 || targetUrl.indexOf(item) != -1) {
              window.location.href = targetUrl;
            }
          });
        }
      } catch (error) {}

      // 使用 volantis.pjax.send 方法传入pjax:send回调函数 参见layout/_partial/scripts/global.ejs
      volantis.pjax.method.send.start();
    });

    document.addEventListener('pjax:complete', function () {
      // 使用 volantis.pjax.push 方法传入重载函数 参见layout/_partial/scripts/global.ejs
      volantis.pjax.method.complete.start();
    });

    document.addEventListener('pjax:error', function (e) {
      if(volantis.debug) {
        console.error(e);
        console.log('pjax error: \n' + JSON.stringify(e));
      }else{
        // 使用 volantis.pjax.error 方法传入pjax:error回调函数 参见layout/_partial/scripts/global.ejs
        volantis.pjax.method.error.start();
        window.location.href = e.triggerElement.href;
      }
    });
</script>

      
    </div>
    <!-- import body_end begin-->
    <!-- import body_end end-->
    <!-- Custom Files bodyEnd begin-->
    
    <!-- Custom Files bodyEnd end-->
    <!-- front-matter body_end begin -->
    <!-- front-matter body_end end -->
  </body>
</html>
